blob: f3c2af6c78e47b52b1c9b346f910548bee77ec7d [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;
Android Dialer3855a6b2017-11-21 14:34:47 -080051import com.android.dialer.feedback.FeedbackComponent;
Eric Erfanian8369df02017-05-03 10:27:13 -070052import com.android.dialer.logging.DialerImpression;
Eric Erfaniand8046e52017-04-06 09:41:50 -070053import com.android.dialer.logging.Logger;
Eric Erfanianccca3152017-02-22 16:32:36 -080054import com.android.dialer.multimedia.MultimediaData;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070055import com.android.dialer.oem.MotorolaUtils;
wangqi97539352017-09-25 11:15:16 -070056import com.android.dialer.phonenumberutil.PhoneNumberHelper;
Eric Erfanian2ca43182017-08-31 06:57:16 -070057import com.android.dialer.postcall.PostCall;
Eric Erfanianccca3152017-02-22 16:32:36 -080058import com.android.incallui.ContactInfoCache.ContactCacheEntry;
59import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
60import com.android.incallui.InCallPresenter.InCallDetailsListener;
61import com.android.incallui.InCallPresenter.InCallEventListener;
62import com.android.incallui.InCallPresenter.InCallState;
63import com.android.incallui.InCallPresenter.InCallStateListener;
64import com.android.incallui.InCallPresenter.IncomingCallListener;
65import com.android.incallui.call.CallList;
66import com.android.incallui.call.DialerCall;
Eric Erfanian2ca43182017-08-31 06:57:16 -070067import com.android.incallui.call.DialerCall.State;
Eric Erfanianccca3152017-02-22 16:32:36 -080068import com.android.incallui.call.DialerCallListener;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070069import com.android.incallui.calllocation.CallLocation;
70import com.android.incallui.calllocation.CallLocationComponent;
Eric Erfanianccca3152017-02-22 16:32:36 -080071import com.android.incallui.incall.protocol.ContactPhotoType;
72import com.android.incallui.incall.protocol.InCallScreen;
73import com.android.incallui.incall.protocol.InCallScreenDelegate;
74import com.android.incallui.incall.protocol.PrimaryCallState;
Eric Erfanian2ca43182017-08-31 06:57:16 -070075import com.android.incallui.incall.protocol.PrimaryCallState.ButtonState;
Eric Erfanianccca3152017-02-22 16:32:36 -080076import com.android.incallui.incall.protocol.PrimaryInfo;
77import com.android.incallui.incall.protocol.SecondaryInfo;
Eric Erfanian90508232017-03-24 09:31:16 -070078import com.android.incallui.videotech.utils.SessionModificationState;
Eric Erfanianccca3152017-02-22 16:32:36 -080079import java.lang.ref.WeakReference;
80
81/**
82 * Controller for the Call Card Fragment. This class listens for changes to InCallState and passes
83 * it along to the fragment.
84 */
85public class CallCardPresenter
86 implements InCallStateListener,
87 IncomingCallListener,
88 InCallDetailsListener,
89 InCallEventListener,
90 InCallScreenDelegate,
Eric Erfanian2ca43182017-08-31 06:57:16 -070091 DialerCallListener {
Eric Erfanianccca3152017-02-22 16:32:36 -080092
93 /**
94 * Amount of time to wait before sending an announcement via the accessibility manager. When the
95 * call state changes to an outgoing or incoming state for the first time, the UI can often be
96 * changing due to call updates or contact lookup. This allows the UI to settle to a stable state
97 * to ensure that the correct information is announced.
98 */
99 private static final long ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS = 500;
100
101 /** Flag to allow the user's current location to be shown during emergency calls. */
102 private static final String CONFIG_ENABLE_EMERGENCY_LOCATION = "config_enable_emergency_location";
103
104 private static final boolean CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT = true;
105
106 /**
107 * Make it possible to not get location during an emergency call if the battery is too low, since
108 * doing so could trigger gps and thus potentially cause the phone to die in the middle of the
109 * call.
110 */
111 private static final String CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION =
112 "min_battery_percent_for_emergency_location";
113
114 private static final long CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT = 10;
115
116 private final Context mContext;
117 private final Handler handler = new Handler();
118
119 private DialerCall mPrimary;
120 private DialerCall mSecondary;
121 private ContactCacheEntry mPrimaryContactInfo;
122 private ContactCacheEntry mSecondaryContactInfo;
123 @Nullable private ContactsPreferences mContactsPreferences;
124 private boolean mIsFullscreen = false;
125 private InCallScreen mInCallScreen;
126 private boolean isInCallScreenReady;
127 private boolean shouldSendAccessibilityEvent;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700128
129 @NonNull private final CallLocation callLocation;
Eric Erfanianccca3152017-02-22 16:32:36 -0800130 private final Runnable sendAccessibilityEventRunnable =
131 new Runnable() {
132 @Override
133 public void run() {
134 shouldSendAccessibilityEvent = !sendAccessibilityEvent(mContext, getUi());
135 LogUtil.i(
136 "CallCardPresenter.sendAccessibilityEventRunnable",
137 "still should send: %b",
138 shouldSendAccessibilityEvent);
139 if (!shouldSendAccessibilityEvent) {
140 handler.removeCallbacks(this);
141 }
142 }
143 };
144
145 public CallCardPresenter(Context context) {
wangqi385a5a12017-09-28 10:44:54 -0700146 LogUtil.i("CallCardPresenter.constructor", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800147 mContext = Assert.isNotNull(context).getApplicationContext();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700148 callLocation = CallLocationComponent.get(mContext).getCallLocation();
Eric Erfanianccca3152017-02-22 16:32:36 -0800149 }
150
151 private static boolean hasCallSubject(DialerCall call) {
152 return !TextUtils.isEmpty(call.getCallSubject());
153 }
154
Android Dialer3855a6b2017-11-21 14:34:47 -0800155 private void addCallFeedbackListener(Context context) {
156 LogUtil.d("CallCardPresenter.addCallFeedbackListener", "Adding call feedback listener");
157 CallList.getInstance().addListener(FeedbackComponent.get(context).getCallFeedbackListener());
158 }
159
Eric Erfanianccca3152017-02-22 16:32:36 -0800160 @Override
161 public void onInCallScreenDelegateInit(InCallScreen inCallScreen) {
162 Assert.isNotNull(inCallScreen);
163 mInCallScreen = inCallScreen;
164 mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext);
165
166 // Call may be null if disconnect happened already.
167 DialerCall call = CallList.getInstance().getFirstCall();
168 if (call != null) {
169 mPrimary = call;
170 if (shouldShowNoteSentToast(mPrimary)) {
171 mInCallScreen.showNoteSentToast();
172 }
173 call.addListener(this);
Android Dialer3855a6b2017-11-21 14:34:47 -0800174 addCallFeedbackListener(mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -0800175 // start processing lookups right away.
176 if (!call.isConferenceCall()) {
177 startContactInfoSearch(call, true, call.getState() == DialerCall.State.INCOMING);
178 } else {
179 updateContactEntry(null, true);
180 }
181 }
182
183 onStateChange(null, InCallPresenter.getInstance().getInCallState(), CallList.getInstance());
184 }
185
186 @Override
187 public void onInCallScreenReady() {
wangqi385a5a12017-09-28 10:44:54 -0700188 LogUtil.i("CallCardPresenter.onInCallScreenReady", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800189 Assert.checkState(!isInCallScreenReady);
190 if (mContactsPreferences != null) {
191 mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY);
192 }
193
Eric Erfanianccca3152017-02-22 16:32:36 -0800194 // Contact search may have completed before ui is ready.
195 if (mPrimaryContactInfo != null) {
196 updatePrimaryDisplayInfo();
197 }
198
199 // Register for call state changes last
200 InCallPresenter.getInstance().addListener(this);
201 InCallPresenter.getInstance().addIncomingCallListener(this);
202 InCallPresenter.getInstance().addDetailsListener(this);
203 InCallPresenter.getInstance().addInCallEventListener(this);
204 isInCallScreenReady = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700205
Eric Erfaniand8046e52017-04-06 09:41:50 -0700206 // Log location impressions
207 if (isOutgoingEmergencyCall(mPrimary)) {
208 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_NEW_EMERGENCY_CALL);
209 } else if (isIncomingEmergencyCall(mPrimary) || isIncomingEmergencyCall(mSecondary)) {
210 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_CALLBACK);
211 }
212
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700213 // Showing the location may have been skipped if the UI wasn't ready during previous layout.
214 if (shouldShowLocation()) {
215 updatePrimaryDisplayInfo();
Eric Erfaniand8046e52017-04-06 09:41:50 -0700216
217 // Log location impressions
218 if (!hasLocationPermission()) {
219 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_NO_LOCATION_PERMISSION);
220 } else if (isBatteryTooLowForEmergencyLocation()) {
221 Logger.get(mContext)
222 .logImpression(DialerImpression.Type.EMERGENCY_BATTERY_TOO_LOW_TO_GET_LOCATION);
223 } else if (!callLocation.canGetLocation(mContext)) {
224 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_CANT_GET_LOCATION);
225 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700226 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800227 }
228
229 @Override
230 public void onInCallScreenUnready() {
wangqi385a5a12017-09-28 10:44:54 -0700231 LogUtil.i("CallCardPresenter.onInCallScreenUnready", null);
Eric Erfanianccca3152017-02-22 16:32:36 -0800232 Assert.checkState(isInCallScreenReady);
233
Eric Erfanianccca3152017-02-22 16:32:36 -0800234 // stop getting call state changes
235 InCallPresenter.getInstance().removeListener(this);
236 InCallPresenter.getInstance().removeIncomingCallListener(this);
237 InCallPresenter.getInstance().removeDetailsListener(this);
238 InCallPresenter.getInstance().removeInCallEventListener(this);
239 if (mPrimary != null) {
240 mPrimary.removeListener(this);
241 }
242
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700243 callLocation.close();
244
Eric Erfanianccca3152017-02-22 16:32:36 -0800245 mPrimary = null;
246 mPrimaryContactInfo = null;
247 mSecondaryContactInfo = null;
248 isInCallScreenReady = false;
249 }
250
251 @Override
252 public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
253 // same logic should happen as with onStateChange()
254 onStateChange(oldState, newState, CallList.getInstance());
255 }
256
257 @Override
258 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700259 Trace.beginSection("CallCardPresenter.onStateChange");
260 LogUtil.v("CallCardPresenter.onStateChange", "oldState: %s, newState: %s", oldState, newState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800261 if (mInCallScreen == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700262 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800263 return;
264 }
265
266 DialerCall primary = null;
267 DialerCall secondary = null;
268
269 if (newState == InCallState.INCOMING) {
270 primary = callList.getIncomingCall();
271 } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
272 primary = callList.getOutgoingCall();
273 if (primary == null) {
274 primary = callList.getPendingOutgoingCall();
275 }
276
277 // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
278 // highest priority call to display as the secondary call.
279 secondary = getCallToDisplay(callList, null, true);
280 } else if (newState == InCallState.INCALL) {
281 primary = getCallToDisplay(callList, null, false);
282 secondary = getCallToDisplay(callList, primary, true);
283 }
284
285 LogUtil.v("CallCardPresenter.onStateChange", "primary call: " + primary);
286 LogUtil.v("CallCardPresenter.onStateChange", "secondary call: " + secondary);
287
288 final boolean primaryChanged =
289 !(DialerCall.areSame(mPrimary, primary) && DialerCall.areSameNumber(mPrimary, primary));
290 final boolean secondaryChanged =
291 !(DialerCall.areSame(mSecondary, secondary)
292 && DialerCall.areSameNumber(mSecondary, secondary));
293
294 mSecondary = secondary;
295 DialerCall previousPrimary = mPrimary;
296 mPrimary = primary;
297
298 if (mPrimary != null) {
299 InCallPresenter.getInstance().onForegroundCallChanged(mPrimary);
300 mInCallScreen.updateInCallScreenColors();
301 }
302
303 if (primaryChanged && shouldShowNoteSentToast(primary)) {
304 mInCallScreen.showNoteSentToast();
305 }
306
307 // Refresh primary call information if either:
308 // 1. Primary call changed.
309 // 2. The call's ability to manage conference has changed.
310 if (shouldRefreshPrimaryInfo(primaryChanged)) {
311 // primary call has changed
312 if (previousPrimary != null) {
313 previousPrimary.removeListener(this);
314 }
315 mPrimary.addListener(this);
316
317 mPrimaryContactInfo =
318 ContactInfoCache.buildCacheEntryFromCall(
319 mContext, mPrimary, mPrimary.getState() == DialerCall.State.INCOMING);
320 updatePrimaryDisplayInfo();
321 maybeStartSearch(mPrimary, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800322 }
323
324 if (previousPrimary != null && mPrimary == null) {
325 previousPrimary.removeListener(this);
326 }
327
wangqic8cf79e2017-10-17 09:21:00 -0700328 if (secondaryChanged) {
329 if (mSecondary == null) {
330 // Secondary call may have ended. Update the ui.
331 mSecondaryContactInfo = null;
332 updateSecondaryDisplayInfo();
333 } else {
334 // secondary call has changed
335 mSecondaryContactInfo =
336 ContactInfoCache.buildCacheEntryFromCall(
337 mContext, mSecondary, mSecondary.getState() == DialerCall.State.INCOMING);
338 updateSecondaryDisplayInfo();
339 maybeStartSearch(mSecondary, false);
340 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800341 }
342
343 // Set the call state
344 int callState = DialerCall.State.IDLE;
345 if (mPrimary != null) {
346 callState = mPrimary.getState();
347 updatePrimaryCallState();
348 } else {
349 getUi().setCallState(PrimaryCallState.createEmptyPrimaryCallState());
350 }
351
352 maybeShowManageConferenceCallButton();
353
354 // Hide the end call button instantly if we're receiving an incoming call.
355 getUi()
356 .setEndCallButtonEnabled(
357 shouldShowEndCallButton(mPrimary, callState),
358 callState != DialerCall.State.INCOMING /* animate */);
359
360 maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700361 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800362 }
363
364 @Override
365 public void onDetailsChanged(DialerCall call, Details details) {
366 updatePrimaryCallState();
367
368 if (call.can(Details.CAPABILITY_MANAGE_CONFERENCE)
369 != details.can(Details.CAPABILITY_MANAGE_CONFERENCE)) {
370 maybeShowManageConferenceCallButton();
371 }
372 }
373
374 @Override
375 public void onDialerCallDisconnect() {}
376
377 @Override
378 public void onDialerCallUpdate() {
379 // No-op; specific call updates handled elsewhere.
380 }
381
382 @Override
383 public void onWiFiToLteHandover() {}
384
385 @Override
386 public void onHandoverToWifiFailure() {}
387
Eric Erfanianc857f902017-05-15 14:05:33 -0700388 @Override
389 public void onInternationalCallOnWifi() {}
390
Eric Erfanian2ca43182017-08-31 06:57:16 -0700391 @Override
392 public void onEnrichedCallSessionUpdate() {
393 LogUtil.enterBlock("CallCardPresenter.onEnrichedCallSessionUpdate");
394 updatePrimaryDisplayInfo();
395 }
396
Eric Erfanianccca3152017-02-22 16:32:36 -0800397 /** Handles a change to the child number by refreshing the primary call info. */
398 @Override
399 public void onDialerCallChildNumberChange() {
400 LogUtil.v("CallCardPresenter.onDialerCallChildNumberChange", "");
401
402 if (mPrimary == null) {
403 return;
404 }
405 updatePrimaryDisplayInfo();
406 }
407
408 /** Handles a change to the last forwarding number by refreshing the primary call info. */
409 @Override
410 public void onDialerCallLastForwardedNumberChange() {
411 LogUtil.v("CallCardPresenter.onDialerCallLastForwardedNumberChange", "");
412
413 if (mPrimary == null) {
414 return;
415 }
416 updatePrimaryDisplayInfo();
417 updatePrimaryCallState();
418 }
419
420 @Override
421 public void onDialerCallUpgradeToVideo() {}
422
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700423 /** Handles a change to the session modification state for a call. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800424 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700425 public void onDialerCallSessionModificationStateChange() {
426 LogUtil.enterBlock("CallCardPresenter.onDialerCallSessionModificationStateChange");
Eric Erfanianccca3152017-02-22 16:32:36 -0800427
428 if (mPrimary == null) {
429 return;
430 }
431 getUi()
432 .setEndCallButtonEnabled(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700433 mPrimary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700434 != SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800435 true /* shouldAnimate */);
436 updatePrimaryCallState();
437 }
438
Eric Erfanianccca3152017-02-22 16:32:36 -0800439 private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
440 if (mPrimary == null) {
441 return false;
442 }
443 return primaryChanged
444 || mInCallScreen.isManageConferenceVisible() != shouldShowManageConference();
445 }
446
447 private void updatePrimaryCallState() {
448 if (getUi() != null && mPrimary != null) {
449 boolean isWorkCall =
450 mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL)
451 || (mPrimaryContactInfo != null
452 && mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
453 boolean isHdAudioCall =
454 isPrimaryCallActive() && mPrimary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700455 boolean isAttemptingHdAudioCall =
456 !isHdAudioCall
457 && !mPrimary.hasProperty(DialerCall.PROPERTY_CODEC_KNOWN)
458 && MotorolaUtils.shouldBlinkHdIconWhenConnectingCall(mContext);
459
460 boolean isBusiness = mPrimaryContactInfo != null && mPrimaryContactInfo.isBusiness;
461
Eric Erfanianccca3152017-02-22 16:32:36 -0800462 // Check for video state change and update the visibility of the contact photo. The contact
463 // photo is hidden when the incoming video surface is shown.
464 // The contact photo visibility can also change in setPrimary().
465 boolean shouldShowContactPhoto =
466 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
467 getUi()
468 .setCallState(
469 new PrimaryCallState(
470 mPrimary.getState(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700471 mPrimary.isVideoCall(),
472 mPrimary.getVideoTech().getSessionModificationState(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800473 mPrimary.getDisconnectCause(),
474 getConnectionLabel(),
475 getCallStateIcon(),
476 getGatewayNumber(),
477 shouldShowCallSubject(mPrimary) ? mPrimary.getCallSubject() : null,
wangqi97539352017-09-25 11:15:16 -0700478 PhoneNumberHelper.formatNumber(
479 mPrimary.getCallbackNumber(), mPrimary.getSimCountryIso()),
Eric Erfanianccca3152017-02-22 16:32:36 -0800480 mPrimary.hasProperty(Details.PROPERTY_WIFI),
Eric Erfanian83b20212017-05-31 08:53:10 -0700481 mPrimary.isConferenceCall()
482 && !mPrimary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE),
Eric Erfanianccca3152017-02-22 16:32:36 -0800483 isWorkCall,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700484 isAttemptingHdAudioCall,
Eric Erfanianccca3152017-02-22 16:32:36 -0800485 isHdAudioCall,
486 !TextUtils.isEmpty(mPrimary.getLastForwardedNumber()),
487 shouldShowContactPhoto,
488 mPrimary.getConnectTimeMillis(),
wangqi9982f0d2017-10-11 17:46:07 -0700489 mPrimary.isVoiceMailNumber(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700490 mPrimary.isRemotelyHeld(),
Eric Erfanian2ca43182017-08-31 06:57:16 -0700491 isBusiness,
492 supports2ndCallOnHold(),
493 getSwapToSecondaryButtonState(),
494 mPrimary.isAssistedDialed(),
erfaniand0f207f2017-10-11 12:23:29 -0700495 null,
496 mPrimary.getAssistedDialingExtras()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800497
498 InCallActivity activity =
499 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
500 if (activity != null) {
501 activity.onPrimaryCallStateChanged();
502 }
503 }
504 }
505
Eric Erfanian2ca43182017-08-31 06:57:16 -0700506 private @ButtonState int getSwapToSecondaryButtonState() {
507 if (mSecondary == null) {
508 return ButtonState.NOT_SUPPORT;
509 }
510 if (mPrimary.getState() == State.ACTIVE) {
511 return ButtonState.ENABLED;
512 }
513 return ButtonState.DISABLED;
514 }
515
Eric Erfanianccca3152017-02-22 16:32:36 -0800516 /** Only show the conference call button if we can manage the conference. */
517 private void maybeShowManageConferenceCallButton() {
518 getUi().showManageConferenceCallButton(shouldShowManageConference());
519 }
520
521 /**
522 * Determines if the manage conference button should be visible, based on the current primary
523 * call.
524 *
525 * @return {@code True} if the manage conference button should be visible.
526 */
527 private boolean shouldShowManageConference() {
528 if (mPrimary == null) {
529 return false;
530 }
531
532 return mPrimary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)
533 && !mIsFullscreen;
534 }
535
Eric Erfanian2ca43182017-08-31 06:57:16 -0700536 private boolean supports2ndCallOnHold() {
537 DialerCall firstCall = CallList.getInstance().getActiveOrBackgroundCall();
538 DialerCall incomingCall = CallList.getInstance().getIncomingCall();
539 if (firstCall != null && incomingCall != null && firstCall != incomingCall) {
540 return incomingCall.can(Details.CAPABILITY_HOLD);
541 }
542 return true;
543 }
544
Eric Erfanianccca3152017-02-22 16:32:36 -0800545 @Override
546 public void onCallStateButtonClicked() {
547 Intent broadcastIntent = Bindings.get(mContext).getCallStateButtonBroadcastIntent(mContext);
548 if (broadcastIntent != null) {
549 LogUtil.v(
550 "CallCardPresenter.onCallStateButtonClicked",
551 "sending call state button broadcast: " + broadcastIntent);
552 mContext.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
553 }
554 }
555
556 @Override
557 public void onManageConferenceClicked() {
558 InCallActivity activity =
559 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
560 activity.showConferenceFragment(true);
561 }
562
563 @Override
564 public void onShrinkAnimationComplete() {
565 InCallPresenter.getInstance().onShrinkAnimationComplete();
566 }
567
Eric Erfanianccca3152017-02-22 16:32:36 -0800568 private void maybeStartSearch(DialerCall call, boolean isPrimary) {
569 // no need to start search for conference calls which show generic info.
570 if (call != null && !call.isConferenceCall()) {
571 startContactInfoSearch(call, isPrimary, call.getState() == DialerCall.State.INCOMING);
572 }
573 }
574
Eric Erfanianccca3152017-02-22 16:32:36 -0800575 /** Starts a query for more contact data for the save primary and secondary calls. */
576 private void startContactInfoSearch(
577 final DialerCall call, final boolean isPrimary, boolean isIncoming) {
578 final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
579
580 cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
581 }
582
583 private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
584 final boolean entryMatchesExistingCall =
585 (isPrimary && mPrimary != null && TextUtils.equals(callId, mPrimary.getId()))
586 || (!isPrimary && mSecondary != null && TextUtils.equals(callId, mSecondary.getId()));
587 if (entryMatchesExistingCall) {
588 updateContactEntry(entry, isPrimary);
589 } else {
590 LogUtil.e(
591 "CallCardPresenter.onContactInfoComplete",
592 "dropping stale contact lookup info for " + callId);
593 }
594
595 final DialerCall call = CallList.getInstance().getCallById(callId);
596 if (call != null) {
597 call.getLogState().contactLookupResult = entry.contactLookupResult;
598 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700599 if (entry.lookupUri != null) {
600 CallerInfoUtils.sendViewNotification(mContext, entry.lookupUri);
Eric Erfanianccca3152017-02-22 16:32:36 -0800601 }
602 }
603
604 private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
605 if (getUi() == null) {
606 return;
607 }
608
609 if (entry.photo != null) {
610 if (mPrimary != null && callId.equals(mPrimary.getId())) {
611 updateContactEntry(entry, true /* isPrimary */);
612 } else if (mSecondary != null && callId.equals(mSecondary.getId())) {
613 updateContactEntry(entry, false /* isPrimary */);
614 }
615 }
616 }
617
618 private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
619 if (isPrimary) {
620 mPrimaryContactInfo = entry;
621 updatePrimaryDisplayInfo();
622 } else {
623 mSecondaryContactInfo = entry;
624 updateSecondaryDisplayInfo();
625 }
626 }
627
628 /**
629 * Get the highest priority call to display. Goes through the calls and chooses which to return
630 * based on priority of which type of call to display to the user. Callers can use the "ignore"
631 * feature to get the second best call by passing a previously found primary call as ignore.
632 *
633 * @param ignore A call to ignore if found.
634 */
635 private DialerCall getCallToDisplay(
636 CallList callList, DialerCall ignore, boolean skipDisconnected) {
637 // Active calls come second. An active call always gets precedent.
638 DialerCall retval = callList.getActiveCall();
639 if (retval != null && retval != ignore) {
640 return retval;
641 }
642
643 // Sometimes there is intemediate state that two calls are in active even one is about
644 // to be on hold.
645 retval = callList.getSecondActiveCall();
646 if (retval != null && retval != ignore) {
647 return retval;
648 }
649
650 // Disconnected calls get primary position if there are no active calls
651 // to let user know quickly what call has disconnected. Disconnected
652 // calls are very short lived.
653 if (!skipDisconnected) {
654 retval = callList.getDisconnectingCall();
655 if (retval != null && retval != ignore) {
656 return retval;
657 }
658 retval = callList.getDisconnectedCall();
659 if (retval != null && retval != ignore) {
660 return retval;
661 }
662 }
663
664 // Then we go to background call (calls on hold)
665 retval = callList.getBackgroundCall();
666 if (retval != null && retval != ignore) {
667 return retval;
668 }
669
670 // Lastly, we go to a second background call.
671 retval = callList.getSecondBackgroundCall();
672
673 return retval;
674 }
675
676 private void updatePrimaryDisplayInfo() {
677 if (mInCallScreen == null) {
678 // TODO: May also occur if search result comes back after ui is destroyed. Look into
679 // removing that case completely.
680 LogUtil.v(
681 "CallCardPresenter.updatePrimaryDisplayInfo",
682 "updatePrimaryDisplayInfo called but ui is null!");
683 return;
684 }
685
686 if (mPrimary == null) {
687 // Clear the primary display info.
688 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
689 return;
690 }
691
692 // Hide the contact photo if we are in a video call and the incoming video surface is
693 // showing.
694 boolean showContactPhoto =
695 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
696
697 // DialerCall placed through a work phone account.
698 boolean hasWorkCallProperty = mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL);
699
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700700 MultimediaData multimediaData = null;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700701 if (mPrimary.getEnrichedCallSession() != null) {
702 multimediaData = mPrimary.getEnrichedCallSession().getMultimediaData();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700703 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800704
705 if (mPrimary.isConferenceCall()) {
706 LogUtil.v(
707 "CallCardPresenter.updatePrimaryDisplayInfo",
708 "update primary display info for conference call.");
709
710 mInCallScreen.setPrimary(
711 new PrimaryInfo(
712 null /* number */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700713 CallerInfoUtils.getConferenceString(
714 mContext, mPrimary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800715 false /* nameIsNumber */,
716 null /* location */,
717 null /* label */,
Eric Erfanian83b20212017-05-31 08:53:10 -0700718 null /* photo */,
Eric Erfanianccca3152017-02-22 16:32:36 -0800719 ContactPhotoType.DEFAULT_PLACEHOLDER,
720 false /* isSipCall */,
721 showContactPhoto,
722 hasWorkCallProperty,
723 false /* isSpam */,
wangqiae6c8ec2017-09-28 17:39:40 -0700724 false /* isLocalContact */,
Eric Erfanianccca3152017-02-22 16:32:36 -0800725 false /* answeringDisconnectsOngoingCall */,
726 shouldShowLocation(),
727 null /* contactInfoLookupKey */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700728 null /* enrichedCallMultimediaData */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700729 true /* showInCallButtonGrid */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700730 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800731 } else if (mPrimaryContactInfo != null) {
732 LogUtil.v(
733 "CallCardPresenter.updatePrimaryDisplayInfo",
734 "update primary display info for " + mPrimaryContactInfo);
735
736 String name = getNameForCall(mPrimaryContactInfo);
737 String number;
738
739 boolean isChildNumberShown = !TextUtils.isEmpty(mPrimary.getChildNumber());
740 boolean isForwardedNumberShown = !TextUtils.isEmpty(mPrimary.getLastForwardedNumber());
741 boolean isCallSubjectShown = shouldShowCallSubject(mPrimary);
742
743 if (isCallSubjectShown) {
744 number = null;
745 } else if (isChildNumberShown) {
746 number = mContext.getString(R.string.child_number, mPrimary.getChildNumber());
747 } else if (isForwardedNumberShown) {
748 // Use last forwarded number instead of second line, if present.
749 number = mPrimary.getLastForwardedNumber();
750 } else {
751 number = mPrimaryContactInfo.number;
752 }
753
754 boolean nameIsNumber = name != null && name.equals(mPrimaryContactInfo.number);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700755
Eric Erfanianccca3152017-02-22 16:32:36 -0800756 // DialerCall with caller that is a work contact.
757 boolean isWorkContact = (mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
758 mInCallScreen.setPrimary(
759 new PrimaryInfo(
760 number,
Eric Erfanianc857f902017-05-15 14:05:33 -0700761 mPrimary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800762 nameIsNumber,
Eric Erfaniand8046e52017-04-06 09:41:50 -0700763 shouldShowLocationAsLabel(nameIsNumber, mPrimaryContactInfo.shouldShowLocation)
764 ? mPrimaryContactInfo.location
765 : null,
Eric Erfanianccca3152017-02-22 16:32:36 -0800766 isChildNumberShown || isCallSubjectShown ? null : mPrimaryContactInfo.label,
767 mPrimaryContactInfo.photo,
768 mPrimaryContactInfo.photoType,
769 mPrimaryContactInfo.isSipCall,
770 showContactPhoto,
771 hasWorkCallProperty || isWorkContact,
772 mPrimary.isSpam(),
wangqiae6c8ec2017-09-28 17:39:40 -0700773 mPrimaryContactInfo.isLocalContact(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800774 mPrimary.answeringDisconnectsForegroundVideoCall(),
775 shouldShowLocation(),
776 mPrimaryContactInfo.lookupKey,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700777 multimediaData,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700778 true /* showInCallButtonGrid */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700779 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800780 } else {
781 // Clear the primary display info.
782 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
783 }
784
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700785 if (isInCallScreenReady) {
786 mInCallScreen.showLocationUi(getLocationFragment());
787 } else {
788 LogUtil.i("CallCardPresenter.updatePrimaryDisplayInfo", "UI not ready, not showing location");
789 }
790 }
791
Eric Erfaniand8046e52017-04-06 09:41:50 -0700792 private static boolean shouldShowLocationAsLabel(
793 boolean nameIsNumber, boolean shouldShowLocation) {
794 if (nameIsNumber) {
795 return true;
796 }
797 if (shouldShowLocation) {
798 return true;
799 }
800 return false;
801 }
802
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700803 private Fragment getLocationFragment() {
804 if (!ConfigProviderBindings.get(mContext)
805 .getBoolean(CONFIG_ENABLE_EMERGENCY_LOCATION, CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT)) {
806 LogUtil.i("CallCardPresenter.getLocationFragment", "disabled by config.");
807 return null;
808 }
809 if (!shouldShowLocation()) {
810 LogUtil.i("CallCardPresenter.getLocationFragment", "shouldn't show location");
811 return null;
812 }
813 if (!hasLocationPermission()) {
814 LogUtil.i("CallCardPresenter.getLocationFragment", "no location permission.");
815 return null;
816 }
817 if (isBatteryTooLowForEmergencyLocation()) {
818 LogUtil.i("CallCardPresenter.getLocationFragment", "low battery.");
819 return null;
820 }
821 if (ActivityCompat.isInMultiWindowMode(mInCallScreen.getInCallScreenFragment().getActivity())) {
822 LogUtil.i("CallCardPresenter.getLocationFragment", "in multi-window mode");
823 return null;
824 }
825 if (mPrimary.isVideoCall()) {
826 LogUtil.i("CallCardPresenter.getLocationFragment", "emergency video calls not supported");
827 return null;
828 }
829 if (!callLocation.canGetLocation(mContext)) {
830 LogUtil.i("CallCardPresenter.getLocationFragment", "can't get current location");
831 return null;
832 }
833 LogUtil.i("CallCardPresenter.getLocationFragment", "returning location fragment");
834 return callLocation.getLocationFragment(mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -0800835 }
836
837 private boolean shouldShowLocation() {
838 if (isOutgoingEmergencyCall(mPrimary)) {
839 LogUtil.i("CallCardPresenter.shouldShowLocation", "new emergency call");
840 return true;
841 } else if (isIncomingEmergencyCall(mPrimary)) {
842 LogUtil.i("CallCardPresenter.shouldShowLocation", "potential emergency callback");
843 return true;
844 } else if (isIncomingEmergencyCall(mSecondary)) {
845 LogUtil.i("CallCardPresenter.shouldShowLocation", "has potential emergency callback");
846 return true;
847 }
848 return false;
849 }
850
851 private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
852 return call != null && !call.isIncoming() && call.isEmergencyCall();
853 }
854
855 private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
856 return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
857 }
858
859 private boolean hasLocationPermission() {
860 return ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION)
861 == PackageManager.PERMISSION_GRANTED;
862 }
863
864 private boolean isBatteryTooLowForEmergencyLocation() {
865 Intent batteryStatus =
866 mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
867 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
868 if (status == BatteryManager.BATTERY_STATUS_CHARGING
869 || status == BatteryManager.BATTERY_STATUS_FULL) {
870 // Plugged in or full battery
871 return false;
872 }
873 int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
874 int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
875 float batteryPercent = (100f * level) / scale;
876 long threshold =
877 ConfigProviderBindings.get(mContext)
878 .getLong(
879 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION,
880 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT);
881 LogUtil.i(
882 "CallCardPresenter.isBatteryTooLowForEmergencyLocation",
883 "percent charged: " + batteryPercent + ", min required charge: " + threshold);
884 return batteryPercent < threshold;
885 }
886
887 private void updateSecondaryDisplayInfo() {
888 if (mInCallScreen == null) {
889 return;
890 }
891
892 if (mSecondary == null) {
893 // Clear the secondary display info.
894 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
895 return;
896 }
897
Eric Erfanian2ca43182017-08-31 06:57:16 -0700898 if (mSecondary.isMergeInProcess()) {
899 LogUtil.i(
900 "CallCardPresenter.updateSecondaryDisplayInfo",
901 "secondary call is merge in process, clearing info");
902 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
903 return;
904 }
905
Eric Erfanianccca3152017-02-22 16:32:36 -0800906 if (mSecondary.isConferenceCall()) {
907 mInCallScreen.setSecondary(
908 new SecondaryInfo(
909 true /* show */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700910 CallerInfoUtils.getConferenceString(
911 mContext, mSecondary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800912 false /* nameIsNumber */,
913 null /* label */,
914 mSecondary.getCallProviderLabel(),
915 true /* isConference */,
916 mSecondary.isVideoCall(),
917 mIsFullscreen));
918 } else if (mSecondaryContactInfo != null) {
919 LogUtil.v("CallCardPresenter.updateSecondaryDisplayInfo", "" + mSecondaryContactInfo);
920 String name = getNameForCall(mSecondaryContactInfo);
921 boolean nameIsNumber = name != null && name.equals(mSecondaryContactInfo.number);
922 mInCallScreen.setSecondary(
923 new SecondaryInfo(
924 true /* show */,
Eric Erfanianc857f902017-05-15 14:05:33 -0700925 mSecondary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800926 nameIsNumber,
927 mSecondaryContactInfo.label,
928 mSecondary.getCallProviderLabel(),
929 false /* isConference */,
930 mSecondary.isVideoCall(),
931 mIsFullscreen));
932 } else {
933 // Clear the secondary display info.
934 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
935 }
936 }
937
938 /** Returns the gateway number for any existing outgoing call. */
939 private String getGatewayNumber() {
940 if (hasOutgoingGatewayCall()) {
941 return DialerCall.getNumberFromHandle(mPrimary.getGatewayInfo().getGatewayAddress());
942 }
943 return null;
944 }
945
946 /**
947 * Returns the label (line of text above the number/name) for any given call. For example,
948 * "calling via [Account/Google Voice]" for outgoing calls.
949 */
950 private String getConnectionLabel() {
951 if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_PHONE_STATE)
952 != PackageManager.PERMISSION_GRANTED) {
953 return null;
954 }
955 StatusHints statusHints = mPrimary.getStatusHints();
956 if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
957 return statusHints.getLabel().toString();
958 }
959
960 if (hasOutgoingGatewayCall() && getUi() != null) {
961 // Return the label for the gateway app on outgoing calls.
962 final PackageManager pm = mContext.getPackageManager();
963 try {
964 ApplicationInfo info =
965 pm.getApplicationInfo(mPrimary.getGatewayInfo().getGatewayProviderPackageName(), 0);
966 return pm.getApplicationLabel(info).toString();
967 } catch (PackageManager.NameNotFoundException e) {
968 LogUtil.e("CallCardPresenter.getConnectionLabel", "gateway Application Not Found.", e);
969 return null;
970 }
971 }
972 return mPrimary.getCallProviderLabel();
973 }
974
975 private Drawable getCallStateIcon() {
976 // Return connection icon if one exists.
977 StatusHints statusHints = mPrimary.getStatusHints();
978 if (statusHints != null && statusHints.getIcon() != null) {
979 Drawable icon = statusHints.getIcon().loadDrawable(mContext);
980 if (icon != null) {
981 return icon;
982 }
983 }
984
985 return null;
986 }
987
988 private boolean hasOutgoingGatewayCall() {
989 // We only display the gateway information while STATE_DIALING so return false for any other
990 // call state.
991 // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
992 // is also called after a contact search completes (call is not present yet). Split the
993 // UI update so it can receive independent updates.
994 if (mPrimary == null) {
995 return false;
996 }
997 return DialerCall.State.isDialing(mPrimary.getState())
998 && mPrimary.getGatewayInfo() != null
999 && !mPrimary.getGatewayInfo().isEmpty();
1000 }
1001
1002 /** Gets the name to display for the call. */
Eric Erfanian2ca43182017-08-31 06:57:16 -07001003 private String getNameForCall(ContactCacheEntry contactInfo) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001004 String preferredName =
1005 ContactDisplayUtils.getPreferredDisplayName(
1006 contactInfo.namePrimary, contactInfo.nameAlternative, mContactsPreferences);
1007 if (TextUtils.isEmpty(preferredName)) {
1008 return contactInfo.number;
1009 }
1010 return preferredName;
1011 }
1012
Eric Erfanianccca3152017-02-22 16:32:36 -08001013 @Override
1014 public void onSecondaryInfoClicked() {
1015 if (mSecondary == null) {
1016 LogUtil.e(
1017 "CallCardPresenter.onSecondaryInfoClicked",
1018 "secondary info clicked but no secondary call.");
1019 return;
1020 }
1021
Eric Erfanian2ca43182017-08-31 06:57:16 -07001022 Logger.get(mContext)
1023 .logCallImpression(
1024 DialerImpression.Type.IN_CALL_SWAP_SECONDARY_BUTTON_PRESSED,
1025 mPrimary.getUniqueCallId(),
1026 mPrimary.getTimeAddedMs());
Eric Erfanianccca3152017-02-22 16:32:36 -08001027 LogUtil.i(
1028 "CallCardPresenter.onSecondaryInfoClicked", "swapping call to foreground: " + mSecondary);
1029 mSecondary.unhold();
1030 }
1031
1032 @Override
1033 public void onEndCallClicked() {
1034 LogUtil.i("CallCardPresenter.onEndCallClicked", "disconnecting call: " + mPrimary);
1035 if (mPrimary != null) {
1036 mPrimary.disconnect();
1037 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001038 PostCall.onDisconnectPressed(mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -08001039 }
1040
1041 /**
1042 * Handles a change to the fullscreen mode of the in-call UI.
1043 *
1044 * @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
1045 */
1046 @Override
1047 public void onFullscreenModeChanged(boolean isFullscreenMode) {
1048 mIsFullscreen = isFullscreenMode;
1049 if (mInCallScreen == null) {
1050 return;
1051 }
1052 maybeShowManageConferenceCallButton();
1053 }
1054
1055 private boolean isPrimaryCallActive() {
1056 return mPrimary != null && mPrimary.getState() == DialerCall.State.ACTIVE;
1057 }
1058
Eric Erfanianccca3152017-02-22 16:32:36 -08001059 private boolean shouldShowEndCallButton(DialerCall primary, int callState) {
1060 if (primary == null) {
1061 return false;
1062 }
1063 if ((!DialerCall.State.isConnectingOrConnected(callState)
1064 && callState != DialerCall.State.DISCONNECTING
1065 && callState != DialerCall.State.DISCONNECTED)
1066 || callState == DialerCall.State.INCOMING) {
1067 return false;
1068 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001069 if (mPrimary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -07001070 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001071 return false;
1072 }
1073 return true;
1074 }
1075
1076 @Override
1077 public void onInCallScreenResumed() {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001078 updatePrimaryDisplayInfo();
1079
Eric Erfanianccca3152017-02-22 16:32:36 -08001080 if (shouldSendAccessibilityEvent) {
1081 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1082 }
1083 }
1084
Eric Erfaniand8046e52017-04-06 09:41:50 -07001085 @Override
Eric Erfanian2ca43182017-08-31 06:57:16 -07001086 public void onInCallScreenPaused() {}
Eric Erfaniand8046e52017-04-06 09:41:50 -07001087
Eric Erfanianccca3152017-02-22 16:32:36 -08001088 static boolean sendAccessibilityEvent(Context context, InCallScreen inCallScreen) {
1089 AccessibilityManager am =
1090 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
1091 if (!am.isEnabled()) {
1092 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "accessibility is off");
1093 return false;
1094 }
1095 if (inCallScreen == null) {
1096 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "incallscreen is null");
1097 return false;
1098 }
1099 Fragment fragment = inCallScreen.getInCallScreenFragment();
1100 if (fragment == null || fragment.getView() == null || fragment.getView().getParent() == null) {
1101 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "fragment/view/parent is null");
1102 return false;
1103 }
1104
1105 DisplayManager displayManager =
1106 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1107 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
1108 boolean screenIsOn = display.getState() == Display.STATE_ON;
1109 LogUtil.d("CallCardPresenter.sendAccessibilityEvent", "screen is on: %b", screenIsOn);
1110 if (!screenIsOn) {
1111 return false;
1112 }
1113
1114 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
1115 inCallScreen.dispatchPopulateAccessibilityEvent(event);
1116 View view = inCallScreen.getInCallScreenFragment().getView();
1117 view.getParent().requestSendAccessibilityEvent(view, event);
1118 return true;
1119 }
1120
1121 private void maybeSendAccessibilityEvent(
1122 InCallState oldState, final InCallState newState, boolean primaryChanged) {
1123 shouldSendAccessibilityEvent = false;
1124 if (mContext == null) {
1125 return;
1126 }
1127 final AccessibilityManager am =
1128 (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
1129 if (!am.isEnabled()) {
1130 return;
1131 }
1132 // Announce the current call if it's new incoming/outgoing call or primary call is changed
1133 // due to switching calls between two ongoing calls (one is on hold).
1134 if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
1135 || (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
1136 || primaryChanged) {
1137 LogUtil.i(
1138 "CallCardPresenter.maybeSendAccessibilityEvent", "schedule accessibility announcement");
1139 shouldSendAccessibilityEvent = true;
1140 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1141 }
1142 }
1143
1144 /**
1145 * Determines whether the call subject should be visible on the UI. For the call subject to be
1146 * visible, the call has to be in an incoming or waiting state, and the subject must not be empty.
1147 *
1148 * @param call The call.
1149 * @return {@code true} if the subject should be shown, {@code false} otherwise.
1150 */
1151 private boolean shouldShowCallSubject(DialerCall call) {
1152 if (call == null) {
1153 return false;
1154 }
1155
1156 boolean isIncomingOrWaiting =
1157 mPrimary.getState() == DialerCall.State.INCOMING
1158 || mPrimary.getState() == DialerCall.State.CALL_WAITING;
1159 return isIncomingOrWaiting
1160 && !TextUtils.isEmpty(call.getCallSubject())
1161 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED
1162 && call.isCallSubjectSupported();
1163 }
1164
1165 /**
1166 * Determines whether the "note sent" toast should be shown. It should be shown for a new outgoing
1167 * call with a subject.
1168 *
1169 * @param call The call
1170 * @return {@code true} if the toast should be shown, {@code false} otherwise.
1171 */
1172 private boolean shouldShowNoteSentToast(DialerCall call) {
1173 return call != null
1174 && hasCallSubject(call)
1175 && (call.getState() == DialerCall.State.DIALING
1176 || call.getState() == DialerCall.State.CONNECTING);
1177 }
1178
1179 private InCallScreen getUi() {
1180 return mInCallScreen;
1181 }
1182
1183 public static class ContactLookupCallback implements ContactInfoCacheCallback {
1184
1185 private final WeakReference<CallCardPresenter> mCallCardPresenter;
1186 private final boolean mIsPrimary;
1187
1188 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
1189 mCallCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
1190 mIsPrimary = isPrimary;
1191 }
1192
1193 @Override
1194 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
1195 CallCardPresenter presenter = mCallCardPresenter.get();
1196 if (presenter != null) {
1197 presenter.onContactInfoComplete(callId, entry, mIsPrimary);
1198 }
1199 }
1200
1201 @Override
1202 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
1203 CallCardPresenter presenter = mCallCardPresenter.get();
1204 if (presenter != null) {
1205 presenter.onImageLoadComplete(callId, entry);
1206 }
1207 }
1208 }
1209}