blob: 653b51aa509507dc8c282c5d3be8215139dc3ca2 [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;
Eric Erfanian2ca43182017-08-31 06:57:16 -070055import com.android.dialer.postcall.PostCall;
Eric Erfanianccca3152017-02-22 16:32:36 -080056import com.android.incallui.ContactInfoCache.ContactCacheEntry;
57import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
58import com.android.incallui.InCallPresenter.InCallDetailsListener;
59import com.android.incallui.InCallPresenter.InCallEventListener;
60import com.android.incallui.InCallPresenter.InCallState;
61import com.android.incallui.InCallPresenter.InCallStateListener;
62import com.android.incallui.InCallPresenter.IncomingCallListener;
63import com.android.incallui.call.CallList;
64import com.android.incallui.call.DialerCall;
Eric Erfanian2ca43182017-08-31 06:57:16 -070065import com.android.incallui.call.DialerCall.State;
Eric Erfanianccca3152017-02-22 16:32:36 -080066import com.android.incallui.call.DialerCallListener;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070067import com.android.incallui.calllocation.CallLocation;
68import com.android.incallui.calllocation.CallLocationComponent;
Eric Erfanianccca3152017-02-22 16:32:36 -080069import com.android.incallui.incall.protocol.ContactPhotoType;
70import com.android.incallui.incall.protocol.InCallScreen;
71import com.android.incallui.incall.protocol.InCallScreenDelegate;
72import com.android.incallui.incall.protocol.PrimaryCallState;
Eric Erfanian2ca43182017-08-31 06:57:16 -070073import com.android.incallui.incall.protocol.PrimaryCallState.ButtonState;
Eric Erfanianccca3152017-02-22 16:32:36 -080074import com.android.incallui.incall.protocol.PrimaryInfo;
75import com.android.incallui.incall.protocol.SecondaryInfo;
Eric Erfanian90508232017-03-24 09:31:16 -070076import com.android.incallui.videotech.utils.SessionModificationState;
Eric Erfanianccca3152017-02-22 16:32:36 -080077import java.lang.ref.WeakReference;
78
79/**
80 * Controller for the Call Card Fragment. This class listens for changes to InCallState and passes
81 * it along to the fragment.
82 */
83public class CallCardPresenter
84 implements InCallStateListener,
85 IncomingCallListener,
86 InCallDetailsListener,
87 InCallEventListener,
88 InCallScreenDelegate,
Eric Erfanian2ca43182017-08-31 06:57:16 -070089 DialerCallListener {
Eric Erfanianccca3152017-02-22 16:32:36 -080090
91 /**
92 * Amount of time to wait before sending an announcement via the accessibility manager. When the
93 * call state changes to an outgoing or incoming state for the first time, the UI can often be
94 * changing due to call updates or contact lookup. This allows the UI to settle to a stable state
95 * to ensure that the correct information is announced.
96 */
97 private static final long ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS = 500;
98
99 /** Flag to allow the user's current location to be shown during emergency calls. */
100 private static final String CONFIG_ENABLE_EMERGENCY_LOCATION = "config_enable_emergency_location";
101
102 private static final boolean CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT = true;
103
104 /**
105 * Make it possible to not get location during an emergency call if the battery is too low, since
106 * doing so could trigger gps and thus potentially cause the phone to die in the middle of the
107 * call.
108 */
109 private static final String CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION =
110 "min_battery_percent_for_emergency_location";
111
112 private static final long CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT = 10;
113
114 private final Context mContext;
115 private final Handler handler = new Handler();
116
117 private DialerCall mPrimary;
118 private DialerCall mSecondary;
119 private ContactCacheEntry mPrimaryContactInfo;
120 private ContactCacheEntry mSecondaryContactInfo;
121 @Nullable private ContactsPreferences mContactsPreferences;
122 private boolean mIsFullscreen = false;
123 private InCallScreen mInCallScreen;
124 private boolean isInCallScreenReady;
125 private boolean shouldSendAccessibilityEvent;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700126
127 @NonNull private final CallLocation callLocation;
Eric Erfanianccca3152017-02-22 16:32:36 -0800128 private final Runnable sendAccessibilityEventRunnable =
129 new Runnable() {
130 @Override
131 public void run() {
132 shouldSendAccessibilityEvent = !sendAccessibilityEvent(mContext, getUi());
133 LogUtil.i(
134 "CallCardPresenter.sendAccessibilityEventRunnable",
135 "still should send: %b",
136 shouldSendAccessibilityEvent);
137 if (!shouldSendAccessibilityEvent) {
138 handler.removeCallbacks(this);
139 }
140 }
141 };
142
143 public CallCardPresenter(Context context) {
144 LogUtil.i("CallCardController.constructor", null);
145 mContext = Assert.isNotNull(context).getApplicationContext();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700146 callLocation = CallLocationComponent.get(mContext).getCallLocation();
Eric Erfanianccca3152017-02-22 16:32:36 -0800147 }
148
149 private static boolean hasCallSubject(DialerCall call) {
150 return !TextUtils.isEmpty(call.getCallSubject());
151 }
152
153 @Override
154 public void onInCallScreenDelegateInit(InCallScreen inCallScreen) {
155 Assert.isNotNull(inCallScreen);
156 mInCallScreen = inCallScreen;
157 mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext);
158
159 // Call may be null if disconnect happened already.
160 DialerCall call = CallList.getInstance().getFirstCall();
161 if (call != null) {
162 mPrimary = call;
163 if (shouldShowNoteSentToast(mPrimary)) {
164 mInCallScreen.showNoteSentToast();
165 }
166 call.addListener(this);
167
168 // start processing lookups right away.
169 if (!call.isConferenceCall()) {
170 startContactInfoSearch(call, true, call.getState() == DialerCall.State.INCOMING);
171 } else {
172 updateContactEntry(null, true);
173 }
174 }
175
176 onStateChange(null, InCallPresenter.getInstance().getInCallState(), CallList.getInstance());
177 }
178
179 @Override
180 public void onInCallScreenReady() {
181 LogUtil.i("CallCardController.onInCallScreenReady", null);
182 Assert.checkState(!isInCallScreenReady);
183 if (mContactsPreferences != null) {
184 mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY);
185 }
186
Eric Erfanianccca3152017-02-22 16:32:36 -0800187 // Contact search may have completed before ui is ready.
188 if (mPrimaryContactInfo != null) {
189 updatePrimaryDisplayInfo();
190 }
191
192 // Register for call state changes last
193 InCallPresenter.getInstance().addListener(this);
194 InCallPresenter.getInstance().addIncomingCallListener(this);
195 InCallPresenter.getInstance().addDetailsListener(this);
196 InCallPresenter.getInstance().addInCallEventListener(this);
197 isInCallScreenReady = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700198
Eric Erfaniand8046e52017-04-06 09:41:50 -0700199 // Log location impressions
200 if (isOutgoingEmergencyCall(mPrimary)) {
201 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_NEW_EMERGENCY_CALL);
202 } else if (isIncomingEmergencyCall(mPrimary) || isIncomingEmergencyCall(mSecondary)) {
203 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_CALLBACK);
204 }
205
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700206 // Showing the location may have been skipped if the UI wasn't ready during previous layout.
207 if (shouldShowLocation()) {
208 updatePrimaryDisplayInfo();
Eric Erfaniand8046e52017-04-06 09:41:50 -0700209
210 // Log location impressions
211 if (!hasLocationPermission()) {
212 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_NO_LOCATION_PERMISSION);
213 } else if (isBatteryTooLowForEmergencyLocation()) {
214 Logger.get(mContext)
215 .logImpression(DialerImpression.Type.EMERGENCY_BATTERY_TOO_LOW_TO_GET_LOCATION);
216 } else if (!callLocation.canGetLocation(mContext)) {
217 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_CANT_GET_LOCATION);
218 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700219 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800220 }
221
222 @Override
223 public void onInCallScreenUnready() {
224 LogUtil.i("CallCardController.onInCallScreenUnready", null);
225 Assert.checkState(isInCallScreenReady);
226
Eric Erfanianccca3152017-02-22 16:32:36 -0800227 // stop getting call state changes
228 InCallPresenter.getInstance().removeListener(this);
229 InCallPresenter.getInstance().removeIncomingCallListener(this);
230 InCallPresenter.getInstance().removeDetailsListener(this);
231 InCallPresenter.getInstance().removeInCallEventListener(this);
232 if (mPrimary != null) {
233 mPrimary.removeListener(this);
234 }
235
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700236 callLocation.close();
237
Eric Erfanianccca3152017-02-22 16:32:36 -0800238 mPrimary = null;
239 mPrimaryContactInfo = null;
240 mSecondaryContactInfo = null;
241 isInCallScreenReady = false;
242 }
243
244 @Override
245 public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
246 // same logic should happen as with onStateChange()
247 onStateChange(oldState, newState, CallList.getInstance());
248 }
249
250 @Override
251 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700252 Trace.beginSection("CallCardPresenter.onStateChange");
253 LogUtil.v("CallCardPresenter.onStateChange", "oldState: %s, newState: %s", oldState, newState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800254 if (mInCallScreen == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700255 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800256 return;
257 }
258
259 DialerCall primary = null;
260 DialerCall secondary = null;
261
262 if (newState == InCallState.INCOMING) {
263 primary = callList.getIncomingCall();
264 } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
265 primary = callList.getOutgoingCall();
266 if (primary == null) {
267 primary = callList.getPendingOutgoingCall();
268 }
269
270 // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
271 // highest priority call to display as the secondary call.
272 secondary = getCallToDisplay(callList, null, true);
273 } else if (newState == InCallState.INCALL) {
274 primary = getCallToDisplay(callList, null, false);
275 secondary = getCallToDisplay(callList, primary, true);
276 }
277
278 LogUtil.v("CallCardPresenter.onStateChange", "primary call: " + primary);
279 LogUtil.v("CallCardPresenter.onStateChange", "secondary call: " + secondary);
280
281 final boolean primaryChanged =
282 !(DialerCall.areSame(mPrimary, primary) && DialerCall.areSameNumber(mPrimary, primary));
283 final boolean secondaryChanged =
284 !(DialerCall.areSame(mSecondary, secondary)
285 && DialerCall.areSameNumber(mSecondary, secondary));
286
287 mSecondary = secondary;
288 DialerCall previousPrimary = mPrimary;
289 mPrimary = primary;
290
291 if (mPrimary != null) {
292 InCallPresenter.getInstance().onForegroundCallChanged(mPrimary);
293 mInCallScreen.updateInCallScreenColors();
294 }
295
296 if (primaryChanged && shouldShowNoteSentToast(primary)) {
297 mInCallScreen.showNoteSentToast();
298 }
299
300 // Refresh primary call information if either:
301 // 1. Primary call changed.
302 // 2. The call's ability to manage conference has changed.
303 if (shouldRefreshPrimaryInfo(primaryChanged)) {
304 // primary call has changed
305 if (previousPrimary != null) {
306 previousPrimary.removeListener(this);
307 }
308 mPrimary.addListener(this);
309
310 mPrimaryContactInfo =
311 ContactInfoCache.buildCacheEntryFromCall(
312 mContext, mPrimary, mPrimary.getState() == DialerCall.State.INCOMING);
313 updatePrimaryDisplayInfo();
314 maybeStartSearch(mPrimary, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800315 }
316
317 if (previousPrimary != null && mPrimary == null) {
318 previousPrimary.removeListener(this);
319 }
320
321 if (mSecondary == null) {
322 // Secondary call may have ended. Update the ui.
323 mSecondaryContactInfo = null;
324 updateSecondaryDisplayInfo();
325 } else if (secondaryChanged) {
326 // secondary call has changed
327 mSecondaryContactInfo =
328 ContactInfoCache.buildCacheEntryFromCall(
329 mContext, mSecondary, mSecondary.getState() == DialerCall.State.INCOMING);
330 updateSecondaryDisplayInfo();
331 maybeStartSearch(mSecondary, false);
Eric Erfanianccca3152017-02-22 16:32:36 -0800332 }
333
334 // Set the call state
335 int callState = DialerCall.State.IDLE;
336 if (mPrimary != null) {
337 callState = mPrimary.getState();
338 updatePrimaryCallState();
339 } else {
340 getUi().setCallState(PrimaryCallState.createEmptyPrimaryCallState());
341 }
342
343 maybeShowManageConferenceCallButton();
344
345 // Hide the end call button instantly if we're receiving an incoming call.
346 getUi()
347 .setEndCallButtonEnabled(
348 shouldShowEndCallButton(mPrimary, callState),
349 callState != DialerCall.State.INCOMING /* animate */);
350
351 maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700352 Trace.endSection();
Eric Erfanianccca3152017-02-22 16:32:36 -0800353 }
354
355 @Override
356 public void onDetailsChanged(DialerCall call, Details details) {
357 updatePrimaryCallState();
358
359 if (call.can(Details.CAPABILITY_MANAGE_CONFERENCE)
360 != details.can(Details.CAPABILITY_MANAGE_CONFERENCE)) {
361 maybeShowManageConferenceCallButton();
362 }
363 }
364
365 @Override
366 public void onDialerCallDisconnect() {}
367
368 @Override
369 public void onDialerCallUpdate() {
370 // No-op; specific call updates handled elsewhere.
371 }
372
373 @Override
374 public void onWiFiToLteHandover() {}
375
376 @Override
377 public void onHandoverToWifiFailure() {}
378
Eric Erfanianc857f902017-05-15 14:05:33 -0700379 @Override
380 public void onInternationalCallOnWifi() {}
381
Eric Erfanian2ca43182017-08-31 06:57:16 -0700382 @Override
383 public void onEnrichedCallSessionUpdate() {
384 LogUtil.enterBlock("CallCardPresenter.onEnrichedCallSessionUpdate");
385 updatePrimaryDisplayInfo();
386 }
387
Eric Erfanianccca3152017-02-22 16:32:36 -0800388 /** Handles a change to the child number by refreshing the primary call info. */
389 @Override
390 public void onDialerCallChildNumberChange() {
391 LogUtil.v("CallCardPresenter.onDialerCallChildNumberChange", "");
392
393 if (mPrimary == null) {
394 return;
395 }
396 updatePrimaryDisplayInfo();
397 }
398
399 /** Handles a change to the last forwarding number by refreshing the primary call info. */
400 @Override
401 public void onDialerCallLastForwardedNumberChange() {
402 LogUtil.v("CallCardPresenter.onDialerCallLastForwardedNumberChange", "");
403
404 if (mPrimary == null) {
405 return;
406 }
407 updatePrimaryDisplayInfo();
408 updatePrimaryCallState();
409 }
410
411 @Override
412 public void onDialerCallUpgradeToVideo() {}
413
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700414 /** Handles a change to the session modification state for a call. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800415 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700416 public void onDialerCallSessionModificationStateChange() {
417 LogUtil.enterBlock("CallCardPresenter.onDialerCallSessionModificationStateChange");
Eric Erfanianccca3152017-02-22 16:32:36 -0800418
419 if (mPrimary == null) {
420 return;
421 }
422 getUi()
423 .setEndCallButtonEnabled(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700424 mPrimary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700425 != SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800426 true /* shouldAnimate */);
427 updatePrimaryCallState();
428 }
429
Eric Erfanianccca3152017-02-22 16:32:36 -0800430 private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
431 if (mPrimary == null) {
432 return false;
433 }
434 return primaryChanged
435 || mInCallScreen.isManageConferenceVisible() != shouldShowManageConference();
436 }
437
438 private void updatePrimaryCallState() {
439 if (getUi() != null && mPrimary != null) {
440 boolean isWorkCall =
441 mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL)
442 || (mPrimaryContactInfo != null
443 && mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
444 boolean isHdAudioCall =
445 isPrimaryCallActive() && mPrimary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700446 boolean isAttemptingHdAudioCall =
447 !isHdAudioCall
448 && !mPrimary.hasProperty(DialerCall.PROPERTY_CODEC_KNOWN)
449 && MotorolaUtils.shouldBlinkHdIconWhenConnectingCall(mContext);
450
451 boolean isBusiness = mPrimaryContactInfo != null && mPrimaryContactInfo.isBusiness;
452
Eric Erfanianccca3152017-02-22 16:32:36 -0800453 // Check for video state change and update the visibility of the contact photo. The contact
454 // photo is hidden when the incoming video surface is shown.
455 // The contact photo visibility can also change in setPrimary().
456 boolean shouldShowContactPhoto =
457 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
458 getUi()
459 .setCallState(
460 new PrimaryCallState(
461 mPrimary.getState(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700462 mPrimary.isVideoCall(),
463 mPrimary.getVideoTech().getSessionModificationState(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800464 mPrimary.getDisconnectCause(),
465 getConnectionLabel(),
466 getCallStateIcon(),
467 getGatewayNumber(),
468 shouldShowCallSubject(mPrimary) ? mPrimary.getCallSubject() : null,
469 mPrimary.getCallbackNumber(),
470 mPrimary.hasProperty(Details.PROPERTY_WIFI),
Eric Erfanian83b20212017-05-31 08:53:10 -0700471 mPrimary.isConferenceCall()
472 && !mPrimary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE),
Eric Erfanianccca3152017-02-22 16:32:36 -0800473 isWorkCall,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700474 isAttemptingHdAudioCall,
Eric Erfanianccca3152017-02-22 16:32:36 -0800475 isHdAudioCall,
476 !TextUtils.isEmpty(mPrimary.getLastForwardedNumber()),
477 shouldShowContactPhoto,
478 mPrimary.getConnectTimeMillis(),
479 CallerInfoUtils.isVoiceMailNumber(mContext, mPrimary),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700480 mPrimary.isRemotelyHeld(),
Eric Erfanian2ca43182017-08-31 06:57:16 -0700481 isBusiness,
482 supports2ndCallOnHold(),
483 getSwapToSecondaryButtonState(),
484 mPrimary.isAssistedDialed(),
485 null));
Eric Erfanianccca3152017-02-22 16:32:36 -0800486
487 InCallActivity activity =
488 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
489 if (activity != null) {
490 activity.onPrimaryCallStateChanged();
491 }
492 }
493 }
494
Eric Erfanian2ca43182017-08-31 06:57:16 -0700495 private @ButtonState int getSwapToSecondaryButtonState() {
496 if (mSecondary == null) {
497 return ButtonState.NOT_SUPPORT;
498 }
499 if (mPrimary.getState() == State.ACTIVE) {
500 return ButtonState.ENABLED;
501 }
502 return ButtonState.DISABLED;
503 }
504
Eric Erfanianccca3152017-02-22 16:32:36 -0800505 /** Only show the conference call button if we can manage the conference. */
506 private void maybeShowManageConferenceCallButton() {
507 getUi().showManageConferenceCallButton(shouldShowManageConference());
508 }
509
510 /**
511 * Determines if the manage conference button should be visible, based on the current primary
512 * call.
513 *
514 * @return {@code True} if the manage conference button should be visible.
515 */
516 private boolean shouldShowManageConference() {
517 if (mPrimary == null) {
518 return false;
519 }
520
521 return mPrimary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)
522 && !mIsFullscreen;
523 }
524
Eric Erfanian2ca43182017-08-31 06:57:16 -0700525 private boolean supports2ndCallOnHold() {
526 DialerCall firstCall = CallList.getInstance().getActiveOrBackgroundCall();
527 DialerCall incomingCall = CallList.getInstance().getIncomingCall();
528 if (firstCall != null && incomingCall != null && firstCall != incomingCall) {
529 return incomingCall.can(Details.CAPABILITY_HOLD);
530 }
531 return true;
532 }
533
Eric Erfanianccca3152017-02-22 16:32:36 -0800534 @Override
535 public void onCallStateButtonClicked() {
536 Intent broadcastIntent = Bindings.get(mContext).getCallStateButtonBroadcastIntent(mContext);
537 if (broadcastIntent != null) {
538 LogUtil.v(
539 "CallCardPresenter.onCallStateButtonClicked",
540 "sending call state button broadcast: " + broadcastIntent);
541 mContext.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
542 }
543 }
544
545 @Override
546 public void onManageConferenceClicked() {
547 InCallActivity activity =
548 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
549 activity.showConferenceFragment(true);
550 }
551
552 @Override
553 public void onShrinkAnimationComplete() {
554 InCallPresenter.getInstance().onShrinkAnimationComplete();
555 }
556
Eric Erfanianccca3152017-02-22 16:32:36 -0800557 private void maybeStartSearch(DialerCall call, boolean isPrimary) {
558 // no need to start search for conference calls which show generic info.
559 if (call != null && !call.isConferenceCall()) {
560 startContactInfoSearch(call, isPrimary, call.getState() == DialerCall.State.INCOMING);
561 }
562 }
563
Eric Erfanianccca3152017-02-22 16:32:36 -0800564 /** Starts a query for more contact data for the save primary and secondary calls. */
565 private void startContactInfoSearch(
566 final DialerCall call, final boolean isPrimary, boolean isIncoming) {
567 final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
568
569 cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
570 }
571
572 private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
573 final boolean entryMatchesExistingCall =
574 (isPrimary && mPrimary != null && TextUtils.equals(callId, mPrimary.getId()))
575 || (!isPrimary && mSecondary != null && TextUtils.equals(callId, mSecondary.getId()));
576 if (entryMatchesExistingCall) {
577 updateContactEntry(entry, isPrimary);
578 } else {
579 LogUtil.e(
580 "CallCardPresenter.onContactInfoComplete",
581 "dropping stale contact lookup info for " + callId);
582 }
583
584 final DialerCall call = CallList.getInstance().getCallById(callId);
585 if (call != null) {
586 call.getLogState().contactLookupResult = entry.contactLookupResult;
587 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700588 if (entry.lookupUri != null) {
589 CallerInfoUtils.sendViewNotification(mContext, entry.lookupUri);
Eric Erfanianccca3152017-02-22 16:32:36 -0800590 }
591 }
592
593 private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
594 if (getUi() == null) {
595 return;
596 }
597
598 if (entry.photo != null) {
599 if (mPrimary != null && callId.equals(mPrimary.getId())) {
600 updateContactEntry(entry, true /* isPrimary */);
601 } else if (mSecondary != null && callId.equals(mSecondary.getId())) {
602 updateContactEntry(entry, false /* isPrimary */);
603 }
604 }
605 }
606
607 private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
608 if (isPrimary) {
609 mPrimaryContactInfo = entry;
610 updatePrimaryDisplayInfo();
611 } else {
612 mSecondaryContactInfo = entry;
613 updateSecondaryDisplayInfo();
614 }
615 }
616
617 /**
618 * Get the highest priority call to display. Goes through the calls and chooses which to return
619 * based on priority of which type of call to display to the user. Callers can use the "ignore"
620 * feature to get the second best call by passing a previously found primary call as ignore.
621 *
622 * @param ignore A call to ignore if found.
623 */
624 private DialerCall getCallToDisplay(
625 CallList callList, DialerCall ignore, boolean skipDisconnected) {
626 // Active calls come second. An active call always gets precedent.
627 DialerCall retval = callList.getActiveCall();
628 if (retval != null && retval != ignore) {
629 return retval;
630 }
631
632 // Sometimes there is intemediate state that two calls are in active even one is about
633 // to be on hold.
634 retval = callList.getSecondActiveCall();
635 if (retval != null && retval != ignore) {
636 return retval;
637 }
638
639 // Disconnected calls get primary position if there are no active calls
640 // to let user know quickly what call has disconnected. Disconnected
641 // calls are very short lived.
642 if (!skipDisconnected) {
643 retval = callList.getDisconnectingCall();
644 if (retval != null && retval != ignore) {
645 return retval;
646 }
647 retval = callList.getDisconnectedCall();
648 if (retval != null && retval != ignore) {
649 return retval;
650 }
651 }
652
653 // Then we go to background call (calls on hold)
654 retval = callList.getBackgroundCall();
655 if (retval != null && retval != ignore) {
656 return retval;
657 }
658
659 // Lastly, we go to a second background call.
660 retval = callList.getSecondBackgroundCall();
661
662 return retval;
663 }
664
665 private void updatePrimaryDisplayInfo() {
666 if (mInCallScreen == null) {
667 // TODO: May also occur if search result comes back after ui is destroyed. Look into
668 // removing that case completely.
669 LogUtil.v(
670 "CallCardPresenter.updatePrimaryDisplayInfo",
671 "updatePrimaryDisplayInfo called but ui is null!");
672 return;
673 }
674
675 if (mPrimary == null) {
676 // Clear the primary display info.
677 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
678 return;
679 }
680
681 // Hide the contact photo if we are in a video call and the incoming video surface is
682 // showing.
683 boolean showContactPhoto =
684 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
685
686 // DialerCall placed through a work phone account.
687 boolean hasWorkCallProperty = mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL);
688
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700689 MultimediaData multimediaData = null;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700690 if (mPrimary.getEnrichedCallSession() != null) {
691 multimediaData = mPrimary.getEnrichedCallSession().getMultimediaData();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700692 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800693
694 if (mPrimary.isConferenceCall()) {
695 LogUtil.v(
696 "CallCardPresenter.updatePrimaryDisplayInfo",
697 "update primary display info for conference call.");
698
699 mInCallScreen.setPrimary(
700 new PrimaryInfo(
701 null /* number */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700702 CallerInfoUtils.getConferenceString(
703 mContext, mPrimary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800704 false /* nameIsNumber */,
705 null /* location */,
706 null /* label */,
Eric Erfanian83b20212017-05-31 08:53:10 -0700707 null /* photo */,
Eric Erfanianccca3152017-02-22 16:32:36 -0800708 ContactPhotoType.DEFAULT_PLACEHOLDER,
709 false /* isSipCall */,
710 showContactPhoto,
711 hasWorkCallProperty,
712 false /* isSpam */,
713 false /* answeringDisconnectsOngoingCall */,
714 shouldShowLocation(),
715 null /* contactInfoLookupKey */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700716 null /* enrichedCallMultimediaData */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700717 true /* showInCallButtonGrid */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700718 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800719 } else if (mPrimaryContactInfo != null) {
720 LogUtil.v(
721 "CallCardPresenter.updatePrimaryDisplayInfo",
722 "update primary display info for " + mPrimaryContactInfo);
723
724 String name = getNameForCall(mPrimaryContactInfo);
725 String number;
726
727 boolean isChildNumberShown = !TextUtils.isEmpty(mPrimary.getChildNumber());
728 boolean isForwardedNumberShown = !TextUtils.isEmpty(mPrimary.getLastForwardedNumber());
729 boolean isCallSubjectShown = shouldShowCallSubject(mPrimary);
730
731 if (isCallSubjectShown) {
732 number = null;
733 } else if (isChildNumberShown) {
734 number = mContext.getString(R.string.child_number, mPrimary.getChildNumber());
735 } else if (isForwardedNumberShown) {
736 // Use last forwarded number instead of second line, if present.
737 number = mPrimary.getLastForwardedNumber();
738 } else {
739 number = mPrimaryContactInfo.number;
740 }
741
742 boolean nameIsNumber = name != null && name.equals(mPrimaryContactInfo.number);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700743
Eric Erfanianccca3152017-02-22 16:32:36 -0800744 // DialerCall with caller that is a work contact.
745 boolean isWorkContact = (mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
746 mInCallScreen.setPrimary(
747 new PrimaryInfo(
748 number,
Eric Erfanianc857f902017-05-15 14:05:33 -0700749 mPrimary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800750 nameIsNumber,
Eric Erfaniand8046e52017-04-06 09:41:50 -0700751 shouldShowLocationAsLabel(nameIsNumber, mPrimaryContactInfo.shouldShowLocation)
752 ? mPrimaryContactInfo.location
753 : null,
Eric Erfanianccca3152017-02-22 16:32:36 -0800754 isChildNumberShown || isCallSubjectShown ? null : mPrimaryContactInfo.label,
755 mPrimaryContactInfo.photo,
756 mPrimaryContactInfo.photoType,
757 mPrimaryContactInfo.isSipCall,
758 showContactPhoto,
759 hasWorkCallProperty || isWorkContact,
760 mPrimary.isSpam(),
761 mPrimary.answeringDisconnectsForegroundVideoCall(),
762 shouldShowLocation(),
763 mPrimaryContactInfo.lookupKey,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700764 multimediaData,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700765 true /* showInCallButtonGrid */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700766 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800767 } else {
768 // Clear the primary display info.
769 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
770 }
771
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700772 if (isInCallScreenReady) {
773 mInCallScreen.showLocationUi(getLocationFragment());
774 } else {
775 LogUtil.i("CallCardPresenter.updatePrimaryDisplayInfo", "UI not ready, not showing location");
776 }
777 }
778
Eric Erfaniand8046e52017-04-06 09:41:50 -0700779 private static boolean shouldShowLocationAsLabel(
780 boolean nameIsNumber, boolean shouldShowLocation) {
781 if (nameIsNumber) {
782 return true;
783 }
784 if (shouldShowLocation) {
785 return true;
786 }
787 return false;
788 }
789
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700790 private Fragment getLocationFragment() {
791 if (!ConfigProviderBindings.get(mContext)
792 .getBoolean(CONFIG_ENABLE_EMERGENCY_LOCATION, CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT)) {
793 LogUtil.i("CallCardPresenter.getLocationFragment", "disabled by config.");
794 return null;
795 }
796 if (!shouldShowLocation()) {
797 LogUtil.i("CallCardPresenter.getLocationFragment", "shouldn't show location");
798 return null;
799 }
800 if (!hasLocationPermission()) {
801 LogUtil.i("CallCardPresenter.getLocationFragment", "no location permission.");
802 return null;
803 }
804 if (isBatteryTooLowForEmergencyLocation()) {
805 LogUtil.i("CallCardPresenter.getLocationFragment", "low battery.");
806 return null;
807 }
808 if (ActivityCompat.isInMultiWindowMode(mInCallScreen.getInCallScreenFragment().getActivity())) {
809 LogUtil.i("CallCardPresenter.getLocationFragment", "in multi-window mode");
810 return null;
811 }
812 if (mPrimary.isVideoCall()) {
813 LogUtil.i("CallCardPresenter.getLocationFragment", "emergency video calls not supported");
814 return null;
815 }
816 if (!callLocation.canGetLocation(mContext)) {
817 LogUtil.i("CallCardPresenter.getLocationFragment", "can't get current location");
818 return null;
819 }
820 LogUtil.i("CallCardPresenter.getLocationFragment", "returning location fragment");
821 return callLocation.getLocationFragment(mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -0800822 }
823
824 private boolean shouldShowLocation() {
825 if (isOutgoingEmergencyCall(mPrimary)) {
826 LogUtil.i("CallCardPresenter.shouldShowLocation", "new emergency call");
827 return true;
828 } else if (isIncomingEmergencyCall(mPrimary)) {
829 LogUtil.i("CallCardPresenter.shouldShowLocation", "potential emergency callback");
830 return true;
831 } else if (isIncomingEmergencyCall(mSecondary)) {
832 LogUtil.i("CallCardPresenter.shouldShowLocation", "has potential emergency callback");
833 return true;
834 }
835 return false;
836 }
837
838 private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
839 return call != null && !call.isIncoming() && call.isEmergencyCall();
840 }
841
842 private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
843 return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
844 }
845
846 private boolean hasLocationPermission() {
847 return ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION)
848 == PackageManager.PERMISSION_GRANTED;
849 }
850
851 private boolean isBatteryTooLowForEmergencyLocation() {
852 Intent batteryStatus =
853 mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
854 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
855 if (status == BatteryManager.BATTERY_STATUS_CHARGING
856 || status == BatteryManager.BATTERY_STATUS_FULL) {
857 // Plugged in or full battery
858 return false;
859 }
860 int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
861 int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
862 float batteryPercent = (100f * level) / scale;
863 long threshold =
864 ConfigProviderBindings.get(mContext)
865 .getLong(
866 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION,
867 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT);
868 LogUtil.i(
869 "CallCardPresenter.isBatteryTooLowForEmergencyLocation",
870 "percent charged: " + batteryPercent + ", min required charge: " + threshold);
871 return batteryPercent < threshold;
872 }
873
874 private void updateSecondaryDisplayInfo() {
875 if (mInCallScreen == null) {
876 return;
877 }
878
879 if (mSecondary == null) {
880 // Clear the secondary display info.
881 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
882 return;
883 }
884
Eric Erfanian2ca43182017-08-31 06:57:16 -0700885 if (mSecondary.isMergeInProcess()) {
886 LogUtil.i(
887 "CallCardPresenter.updateSecondaryDisplayInfo",
888 "secondary call is merge in process, clearing info");
889 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
890 return;
891 }
892
Eric Erfanianccca3152017-02-22 16:32:36 -0800893 if (mSecondary.isConferenceCall()) {
894 mInCallScreen.setSecondary(
895 new SecondaryInfo(
896 true /* show */,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700897 CallerInfoUtils.getConferenceString(
898 mContext, mSecondary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800899 false /* nameIsNumber */,
900 null /* label */,
901 mSecondary.getCallProviderLabel(),
902 true /* isConference */,
903 mSecondary.isVideoCall(),
904 mIsFullscreen));
905 } else if (mSecondaryContactInfo != null) {
906 LogUtil.v("CallCardPresenter.updateSecondaryDisplayInfo", "" + mSecondaryContactInfo);
907 String name = getNameForCall(mSecondaryContactInfo);
908 boolean nameIsNumber = name != null && name.equals(mSecondaryContactInfo.number);
909 mInCallScreen.setSecondary(
910 new SecondaryInfo(
911 true /* show */,
Eric Erfanianc857f902017-05-15 14:05:33 -0700912 mSecondary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800913 nameIsNumber,
914 mSecondaryContactInfo.label,
915 mSecondary.getCallProviderLabel(),
916 false /* isConference */,
917 mSecondary.isVideoCall(),
918 mIsFullscreen));
919 } else {
920 // Clear the secondary display info.
921 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
922 }
923 }
924
925 /** Returns the gateway number for any existing outgoing call. */
926 private String getGatewayNumber() {
927 if (hasOutgoingGatewayCall()) {
928 return DialerCall.getNumberFromHandle(mPrimary.getGatewayInfo().getGatewayAddress());
929 }
930 return null;
931 }
932
933 /**
934 * Returns the label (line of text above the number/name) for any given call. For example,
935 * "calling via [Account/Google Voice]" for outgoing calls.
936 */
937 private String getConnectionLabel() {
938 if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_PHONE_STATE)
939 != PackageManager.PERMISSION_GRANTED) {
940 return null;
941 }
942 StatusHints statusHints = mPrimary.getStatusHints();
943 if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
944 return statusHints.getLabel().toString();
945 }
946
947 if (hasOutgoingGatewayCall() && getUi() != null) {
948 // Return the label for the gateway app on outgoing calls.
949 final PackageManager pm = mContext.getPackageManager();
950 try {
951 ApplicationInfo info =
952 pm.getApplicationInfo(mPrimary.getGatewayInfo().getGatewayProviderPackageName(), 0);
953 return pm.getApplicationLabel(info).toString();
954 } catch (PackageManager.NameNotFoundException e) {
955 LogUtil.e("CallCardPresenter.getConnectionLabel", "gateway Application Not Found.", e);
956 return null;
957 }
958 }
959 return mPrimary.getCallProviderLabel();
960 }
961
962 private Drawable getCallStateIcon() {
963 // Return connection icon if one exists.
964 StatusHints statusHints = mPrimary.getStatusHints();
965 if (statusHints != null && statusHints.getIcon() != null) {
966 Drawable icon = statusHints.getIcon().loadDrawable(mContext);
967 if (icon != null) {
968 return icon;
969 }
970 }
971
972 return null;
973 }
974
975 private boolean hasOutgoingGatewayCall() {
976 // We only display the gateway information while STATE_DIALING so return false for any other
977 // call state.
978 // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
979 // is also called after a contact search completes (call is not present yet). Split the
980 // UI update so it can receive independent updates.
981 if (mPrimary == null) {
982 return false;
983 }
984 return DialerCall.State.isDialing(mPrimary.getState())
985 && mPrimary.getGatewayInfo() != null
986 && !mPrimary.getGatewayInfo().isEmpty();
987 }
988
989 /** Gets the name to display for the call. */
Eric Erfanian2ca43182017-08-31 06:57:16 -0700990 private String getNameForCall(ContactCacheEntry contactInfo) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800991 String preferredName =
992 ContactDisplayUtils.getPreferredDisplayName(
993 contactInfo.namePrimary, contactInfo.nameAlternative, mContactsPreferences);
994 if (TextUtils.isEmpty(preferredName)) {
995 return contactInfo.number;
996 }
997 return preferredName;
998 }
999
Eric Erfanianccca3152017-02-22 16:32:36 -08001000 @Override
1001 public void onSecondaryInfoClicked() {
1002 if (mSecondary == null) {
1003 LogUtil.e(
1004 "CallCardPresenter.onSecondaryInfoClicked",
1005 "secondary info clicked but no secondary call.");
1006 return;
1007 }
1008
Eric Erfanian2ca43182017-08-31 06:57:16 -07001009 Logger.get(mContext)
1010 .logCallImpression(
1011 DialerImpression.Type.IN_CALL_SWAP_SECONDARY_BUTTON_PRESSED,
1012 mPrimary.getUniqueCallId(),
1013 mPrimary.getTimeAddedMs());
Eric Erfanianccca3152017-02-22 16:32:36 -08001014 LogUtil.i(
1015 "CallCardPresenter.onSecondaryInfoClicked", "swapping call to foreground: " + mSecondary);
1016 mSecondary.unhold();
1017 }
1018
1019 @Override
1020 public void onEndCallClicked() {
1021 LogUtil.i("CallCardPresenter.onEndCallClicked", "disconnecting call: " + mPrimary);
1022 if (mPrimary != null) {
1023 mPrimary.disconnect();
1024 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001025 PostCall.onDisconnectPressed(mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -08001026 }
1027
1028 /**
1029 * Handles a change to the fullscreen mode of the in-call UI.
1030 *
1031 * @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
1032 */
1033 @Override
1034 public void onFullscreenModeChanged(boolean isFullscreenMode) {
1035 mIsFullscreen = isFullscreenMode;
1036 if (mInCallScreen == null) {
1037 return;
1038 }
1039 maybeShowManageConferenceCallButton();
1040 }
1041
1042 private boolean isPrimaryCallActive() {
1043 return mPrimary != null && mPrimary.getState() == DialerCall.State.ACTIVE;
1044 }
1045
Eric Erfanianccca3152017-02-22 16:32:36 -08001046 private boolean shouldShowEndCallButton(DialerCall primary, int callState) {
1047 if (primary == null) {
1048 return false;
1049 }
1050 if ((!DialerCall.State.isConnectingOrConnected(callState)
1051 && callState != DialerCall.State.DISCONNECTING
1052 && callState != DialerCall.State.DISCONNECTED)
1053 || callState == DialerCall.State.INCOMING) {
1054 return false;
1055 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001056 if (mPrimary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -07001057 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001058 return false;
1059 }
1060 return true;
1061 }
1062
1063 @Override
1064 public void onInCallScreenResumed() {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001065 updatePrimaryDisplayInfo();
1066
Eric Erfanianccca3152017-02-22 16:32:36 -08001067 if (shouldSendAccessibilityEvent) {
1068 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1069 }
1070 }
1071
Eric Erfaniand8046e52017-04-06 09:41:50 -07001072 @Override
Eric Erfanian2ca43182017-08-31 06:57:16 -07001073 public void onInCallScreenPaused() {}
Eric Erfaniand8046e52017-04-06 09:41:50 -07001074
Eric Erfanianccca3152017-02-22 16:32:36 -08001075 static boolean sendAccessibilityEvent(Context context, InCallScreen inCallScreen) {
1076 AccessibilityManager am =
1077 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
1078 if (!am.isEnabled()) {
1079 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "accessibility is off");
1080 return false;
1081 }
1082 if (inCallScreen == null) {
1083 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "incallscreen is null");
1084 return false;
1085 }
1086 Fragment fragment = inCallScreen.getInCallScreenFragment();
1087 if (fragment == null || fragment.getView() == null || fragment.getView().getParent() == null) {
1088 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "fragment/view/parent is null");
1089 return false;
1090 }
1091
1092 DisplayManager displayManager =
1093 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1094 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
1095 boolean screenIsOn = display.getState() == Display.STATE_ON;
1096 LogUtil.d("CallCardPresenter.sendAccessibilityEvent", "screen is on: %b", screenIsOn);
1097 if (!screenIsOn) {
1098 return false;
1099 }
1100
1101 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
1102 inCallScreen.dispatchPopulateAccessibilityEvent(event);
1103 View view = inCallScreen.getInCallScreenFragment().getView();
1104 view.getParent().requestSendAccessibilityEvent(view, event);
1105 return true;
1106 }
1107
1108 private void maybeSendAccessibilityEvent(
1109 InCallState oldState, final InCallState newState, boolean primaryChanged) {
1110 shouldSendAccessibilityEvent = false;
1111 if (mContext == null) {
1112 return;
1113 }
1114 final AccessibilityManager am =
1115 (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
1116 if (!am.isEnabled()) {
1117 return;
1118 }
1119 // Announce the current call if it's new incoming/outgoing call or primary call is changed
1120 // due to switching calls between two ongoing calls (one is on hold).
1121 if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
1122 || (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
1123 || primaryChanged) {
1124 LogUtil.i(
1125 "CallCardPresenter.maybeSendAccessibilityEvent", "schedule accessibility announcement");
1126 shouldSendAccessibilityEvent = true;
1127 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1128 }
1129 }
1130
1131 /**
1132 * Determines whether the call subject should be visible on the UI. For the call subject to be
1133 * visible, the call has to be in an incoming or waiting state, and the subject must not be empty.
1134 *
1135 * @param call The call.
1136 * @return {@code true} if the subject should be shown, {@code false} otherwise.
1137 */
1138 private boolean shouldShowCallSubject(DialerCall call) {
1139 if (call == null) {
1140 return false;
1141 }
1142
1143 boolean isIncomingOrWaiting =
1144 mPrimary.getState() == DialerCall.State.INCOMING
1145 || mPrimary.getState() == DialerCall.State.CALL_WAITING;
1146 return isIncomingOrWaiting
1147 && !TextUtils.isEmpty(call.getCallSubject())
1148 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED
1149 && call.isCallSubjectSupported();
1150 }
1151
1152 /**
1153 * Determines whether the "note sent" toast should be shown. It should be shown for a new outgoing
1154 * call with a subject.
1155 *
1156 * @param call The call
1157 * @return {@code true} if the toast should be shown, {@code false} otherwise.
1158 */
1159 private boolean shouldShowNoteSentToast(DialerCall call) {
1160 return call != null
1161 && hasCallSubject(call)
1162 && (call.getState() == DialerCall.State.DIALING
1163 || call.getState() == DialerCall.State.CONNECTING);
1164 }
1165
1166 private InCallScreen getUi() {
1167 return mInCallScreen;
1168 }
1169
1170 public static class ContactLookupCallback implements ContactInfoCacheCallback {
1171
1172 private final WeakReference<CallCardPresenter> mCallCardPresenter;
1173 private final boolean mIsPrimary;
1174
1175 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
1176 mCallCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
1177 mIsPrimary = isPrimary;
1178 }
1179
1180 @Override
1181 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
1182 CallCardPresenter presenter = mCallCardPresenter.get();
1183 if (presenter != null) {
1184 presenter.onContactInfoComplete(callId, entry, mIsPrimary);
1185 }
1186 }
1187
1188 @Override
1189 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
1190 CallCardPresenter presenter = mCallCardPresenter.get();
1191 if (presenter != null) {
1192 presenter.onImageLoadComplete(callId, entry);
1193 }
1194 }
1195 }
1196}