blob: cd06793ce41220d354d580268f5d4da08e3d734f [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 Erfaniand5e47f62017-03-15 14:41:07 -070031import android.support.annotation.NonNull;
Eric Erfanianccca3152017-02-22 16:32:36 -080032import android.support.annotation.Nullable;
33import android.support.v4.app.Fragment;
34import android.support.v4.content.ContextCompat;
35import android.telecom.Call.Details;
36import android.telecom.StatusHints;
37import android.telecom.TelecomManager;
38import android.text.TextUtils;
39import android.view.Display;
40import android.view.View;
41import android.view.accessibility.AccessibilityEvent;
42import android.view.accessibility.AccessibilityManager;
43import com.android.contacts.common.ContactsUtils;
44import com.android.contacts.common.preference.ContactsPreferences;
45import com.android.contacts.common.util.ContactDisplayUtils;
46import com.android.dialer.common.Assert;
47import com.android.dialer.common.ConfigProviderBindings;
48import com.android.dialer.common.LogUtil;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070049import com.android.dialer.compat.ActivityCompat;
Eric Erfanian8369df02017-05-03 10:27:13 -070050import com.android.dialer.logging.DialerImpression;
Eric Erfaniand8046e52017-04-06 09:41:50 -070051import com.android.dialer.logging.Logger;
Eric Erfanianccca3152017-02-22 16:32:36 -080052import com.android.dialer.multimedia.MultimediaData;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070053import com.android.dialer.oem.MotorolaUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080054import com.android.incallui.ContactInfoCache.ContactCacheEntry;
55import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
56import com.android.incallui.InCallPresenter.InCallDetailsListener;
57import com.android.incallui.InCallPresenter.InCallEventListener;
58import com.android.incallui.InCallPresenter.InCallState;
59import com.android.incallui.InCallPresenter.InCallStateListener;
60import com.android.incallui.InCallPresenter.IncomingCallListener;
61import com.android.incallui.call.CallList;
62import com.android.incallui.call.DialerCall;
Eric Erfanianccca3152017-02-22 16:32:36 -080063import com.android.incallui.call.DialerCallListener;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070064import com.android.incallui.calllocation.CallLocation;
65import com.android.incallui.calllocation.CallLocationComponent;
Eric Erfanianccca3152017-02-22 16:32:36 -080066import com.android.incallui.incall.protocol.ContactPhotoType;
67import com.android.incallui.incall.protocol.InCallScreen;
68import com.android.incallui.incall.protocol.InCallScreenDelegate;
69import com.android.incallui.incall.protocol.PrimaryCallState;
70import com.android.incallui.incall.protocol.PrimaryInfo;
71import com.android.incallui.incall.protocol.SecondaryInfo;
Eric Erfanian90508232017-03-24 09:31:16 -070072import com.android.incallui.videotech.utils.SessionModificationState;
Eric Erfanianccca3152017-02-22 16:32:36 -080073import java.lang.ref.WeakReference;
74
75/**
76 * Controller for the Call Card Fragment. This class listens for changes to InCallState and passes
77 * it along to the fragment.
78 */
79public class CallCardPresenter
80 implements InCallStateListener,
81 IncomingCallListener,
82 InCallDetailsListener,
83 InCallEventListener,
84 InCallScreenDelegate,
Eric Erfanian91ce7d22017-06-05 13:35:02 -070085 DialerCallListener {
Eric Erfanianccca3152017-02-22 16:32:36 -080086
87 /**
88 * Amount of time to wait before sending an announcement via the accessibility manager. When the
89 * call state changes to an outgoing or incoming state for the first time, the UI can often be
90 * changing due to call updates or contact lookup. This allows the UI to settle to a stable state
91 * to ensure that the correct information is announced.
92 */
93 private static final long ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS = 500;
94
95 /** Flag to allow the user's current location to be shown during emergency calls. */
96 private static final String CONFIG_ENABLE_EMERGENCY_LOCATION = "config_enable_emergency_location";
97
98 private static final boolean CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT = true;
99
100 /**
101 * Make it possible to not get location during an emergency call if the battery is too low, since
102 * doing so could trigger gps and thus potentially cause the phone to die in the middle of the
103 * call.
104 */
105 private static final String CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION =
106 "min_battery_percent_for_emergency_location";
107
108 private static final long CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT = 10;
109
110 private final Context mContext;
111 private final Handler handler = new Handler();
112
113 private DialerCall mPrimary;
114 private DialerCall mSecondary;
115 private ContactCacheEntry mPrimaryContactInfo;
116 private ContactCacheEntry mSecondaryContactInfo;
117 @Nullable private ContactsPreferences mContactsPreferences;
118 private boolean mIsFullscreen = false;
119 private InCallScreen mInCallScreen;
120 private boolean isInCallScreenReady;
121 private boolean shouldSendAccessibilityEvent;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700122
123 @NonNull private final CallLocation callLocation;
Eric Erfanianccca3152017-02-22 16:32:36 -0800124 private final Runnable sendAccessibilityEventRunnable =
125 new Runnable() {
126 @Override
127 public void run() {
128 shouldSendAccessibilityEvent = !sendAccessibilityEvent(mContext, getUi());
129 LogUtil.i(
130 "CallCardPresenter.sendAccessibilityEventRunnable",
131 "still should send: %b",
132 shouldSendAccessibilityEvent);
133 if (!shouldSendAccessibilityEvent) {
134 handler.removeCallbacks(this);
135 }
136 }
137 };
138
139 public CallCardPresenter(Context context) {
140 LogUtil.i("CallCardController.constructor", null);
141 mContext = Assert.isNotNull(context).getApplicationContext();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700142 callLocation = CallLocationComponent.get(mContext).getCallLocation();
Eric Erfanianccca3152017-02-22 16:32:36 -0800143 }
144
145 private static boolean hasCallSubject(DialerCall call) {
146 return !TextUtils.isEmpty(call.getCallSubject());
147 }
148
149 @Override
150 public void onInCallScreenDelegateInit(InCallScreen inCallScreen) {
151 Assert.isNotNull(inCallScreen);
152 mInCallScreen = inCallScreen;
153 mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(mContext);
154
155 // Call may be null if disconnect happened already.
156 DialerCall call = CallList.getInstance().getFirstCall();
157 if (call != null) {
158 mPrimary = call;
159 if (shouldShowNoteSentToast(mPrimary)) {
160 mInCallScreen.showNoteSentToast();
161 }
162 call.addListener(this);
163
164 // start processing lookups right away.
165 if (!call.isConferenceCall()) {
166 startContactInfoSearch(call, true, call.getState() == DialerCall.State.INCOMING);
167 } else {
168 updateContactEntry(null, true);
169 }
170 }
171
172 onStateChange(null, InCallPresenter.getInstance().getInCallState(), CallList.getInstance());
173 }
174
175 @Override
176 public void onInCallScreenReady() {
177 LogUtil.i("CallCardController.onInCallScreenReady", null);
178 Assert.checkState(!isInCallScreenReady);
179 if (mContactsPreferences != null) {
180 mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY);
181 }
182
Eric Erfanianccca3152017-02-22 16:32:36 -0800183 // Contact search may have completed before ui is ready.
184 if (mPrimaryContactInfo != null) {
185 updatePrimaryDisplayInfo();
186 }
187
188 // Register for call state changes last
189 InCallPresenter.getInstance().addListener(this);
190 InCallPresenter.getInstance().addIncomingCallListener(this);
191 InCallPresenter.getInstance().addDetailsListener(this);
192 InCallPresenter.getInstance().addInCallEventListener(this);
193 isInCallScreenReady = true;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700194
Eric Erfaniand8046e52017-04-06 09:41:50 -0700195 // Log location impressions
196 if (isOutgoingEmergencyCall(mPrimary)) {
197 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_NEW_EMERGENCY_CALL);
198 } else if (isIncomingEmergencyCall(mPrimary) || isIncomingEmergencyCall(mSecondary)) {
199 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_CALLBACK);
200 }
201
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700202 // Showing the location may have been skipped if the UI wasn't ready during previous layout.
203 if (shouldShowLocation()) {
204 updatePrimaryDisplayInfo();
Eric Erfaniand8046e52017-04-06 09:41:50 -0700205
206 // Log location impressions
207 if (!hasLocationPermission()) {
208 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_NO_LOCATION_PERMISSION);
209 } else if (isBatteryTooLowForEmergencyLocation()) {
210 Logger.get(mContext)
211 .logImpression(DialerImpression.Type.EMERGENCY_BATTERY_TOO_LOW_TO_GET_LOCATION);
212 } else if (!callLocation.canGetLocation(mContext)) {
213 Logger.get(mContext).logImpression(DialerImpression.Type.EMERGENCY_CANT_GET_LOCATION);
214 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700215 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800216 }
217
218 @Override
219 public void onInCallScreenUnready() {
220 LogUtil.i("CallCardController.onInCallScreenUnready", null);
221 Assert.checkState(isInCallScreenReady);
222
Eric Erfanianccca3152017-02-22 16:32:36 -0800223 // stop getting call state changes
224 InCallPresenter.getInstance().removeListener(this);
225 InCallPresenter.getInstance().removeIncomingCallListener(this);
226 InCallPresenter.getInstance().removeDetailsListener(this);
227 InCallPresenter.getInstance().removeInCallEventListener(this);
228 if (mPrimary != null) {
229 mPrimary.removeListener(this);
230 }
231
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700232 callLocation.close();
233
Eric Erfanianccca3152017-02-22 16:32:36 -0800234 mPrimary = null;
235 mPrimaryContactInfo = null;
236 mSecondaryContactInfo = null;
237 isInCallScreenReady = false;
238 }
239
240 @Override
241 public void onIncomingCall(InCallState oldState, InCallState newState, DialerCall call) {
242 // same logic should happen as with onStateChange()
243 onStateChange(oldState, newState, CallList.getInstance());
244 }
245
246 @Override
247 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
248 LogUtil.v("CallCardPresenter.onStateChange", "" + newState);
249 if (mInCallScreen == null) {
250 return;
251 }
252
253 DialerCall primary = null;
254 DialerCall secondary = null;
255
256 if (newState == InCallState.INCOMING) {
257 primary = callList.getIncomingCall();
258 } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
259 primary = callList.getOutgoingCall();
260 if (primary == null) {
261 primary = callList.getPendingOutgoingCall();
262 }
263
264 // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
265 // highest priority call to display as the secondary call.
266 secondary = getCallToDisplay(callList, null, true);
267 } else if (newState == InCallState.INCALL) {
268 primary = getCallToDisplay(callList, null, false);
269 secondary = getCallToDisplay(callList, primary, true);
270 }
271
272 LogUtil.v("CallCardPresenter.onStateChange", "primary call: " + primary);
273 LogUtil.v("CallCardPresenter.onStateChange", "secondary call: " + secondary);
274
275 final boolean primaryChanged =
276 !(DialerCall.areSame(mPrimary, primary) && DialerCall.areSameNumber(mPrimary, primary));
277 final boolean secondaryChanged =
278 !(DialerCall.areSame(mSecondary, secondary)
279 && DialerCall.areSameNumber(mSecondary, secondary));
280
281 mSecondary = secondary;
282 DialerCall previousPrimary = mPrimary;
283 mPrimary = primary;
284
285 if (mPrimary != null) {
286 InCallPresenter.getInstance().onForegroundCallChanged(mPrimary);
287 mInCallScreen.updateInCallScreenColors();
288 }
289
290 if (primaryChanged && shouldShowNoteSentToast(primary)) {
291 mInCallScreen.showNoteSentToast();
292 }
293
294 // Refresh primary call information if either:
295 // 1. Primary call changed.
296 // 2. The call's ability to manage conference has changed.
297 if (shouldRefreshPrimaryInfo(primaryChanged)) {
298 // primary call has changed
299 if (previousPrimary != null) {
300 previousPrimary.removeListener(this);
301 }
302 mPrimary.addListener(this);
303
304 mPrimaryContactInfo =
305 ContactInfoCache.buildCacheEntryFromCall(
306 mContext, mPrimary, mPrimary.getState() == DialerCall.State.INCOMING);
307 updatePrimaryDisplayInfo();
308 maybeStartSearch(mPrimary, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800309 }
310
311 if (previousPrimary != null && mPrimary == null) {
312 previousPrimary.removeListener(this);
313 }
314
315 if (mSecondary == null) {
316 // Secondary call may have ended. Update the ui.
317 mSecondaryContactInfo = null;
318 updateSecondaryDisplayInfo();
319 } else if (secondaryChanged) {
320 // secondary call has changed
321 mSecondaryContactInfo =
322 ContactInfoCache.buildCacheEntryFromCall(
323 mContext, mSecondary, mSecondary.getState() == DialerCall.State.INCOMING);
324 updateSecondaryDisplayInfo();
325 maybeStartSearch(mSecondary, false);
Eric Erfanianccca3152017-02-22 16:32:36 -0800326 }
327
328 // Set the call state
329 int callState = DialerCall.State.IDLE;
330 if (mPrimary != null) {
331 callState = mPrimary.getState();
332 updatePrimaryCallState();
333 } else {
334 getUi().setCallState(PrimaryCallState.createEmptyPrimaryCallState());
335 }
336
337 maybeShowManageConferenceCallButton();
338
339 // Hide the end call button instantly if we're receiving an incoming call.
340 getUi()
341 .setEndCallButtonEnabled(
342 shouldShowEndCallButton(mPrimary, callState),
343 callState != DialerCall.State.INCOMING /* animate */);
344
345 maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
346 }
347
348 @Override
349 public void onDetailsChanged(DialerCall call, Details details) {
350 updatePrimaryCallState();
351
352 if (call.can(Details.CAPABILITY_MANAGE_CONFERENCE)
353 != details.can(Details.CAPABILITY_MANAGE_CONFERENCE)) {
354 maybeShowManageConferenceCallButton();
355 }
356 }
357
358 @Override
359 public void onDialerCallDisconnect() {}
360
361 @Override
362 public void onDialerCallUpdate() {
363 // No-op; specific call updates handled elsewhere.
364 }
365
366 @Override
367 public void onWiFiToLteHandover() {}
368
369 @Override
370 public void onHandoverToWifiFailure() {}
371
Eric Erfanianc857f902017-05-15 14:05:33 -0700372 @Override
373 public void onInternationalCallOnWifi() {}
374
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700375 @Override
376 public void onEnrichedCallSessionUpdate() {
377 updatePrimaryDisplayInfo();
378 }
379
Eric Erfanianccca3152017-02-22 16:32:36 -0800380 /** Handles a change to the child number by refreshing the primary call info. */
381 @Override
382 public void onDialerCallChildNumberChange() {
383 LogUtil.v("CallCardPresenter.onDialerCallChildNumberChange", "");
384
385 if (mPrimary == null) {
386 return;
387 }
388 updatePrimaryDisplayInfo();
389 }
390
391 /** Handles a change to the last forwarding number by refreshing the primary call info. */
392 @Override
393 public void onDialerCallLastForwardedNumberChange() {
394 LogUtil.v("CallCardPresenter.onDialerCallLastForwardedNumberChange", "");
395
396 if (mPrimary == null) {
397 return;
398 }
399 updatePrimaryDisplayInfo();
400 updatePrimaryCallState();
401 }
402
403 @Override
404 public void onDialerCallUpgradeToVideo() {}
405
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700406 /** Handles a change to the session modification state for a call. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800407 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700408 public void onDialerCallSessionModificationStateChange() {
409 LogUtil.enterBlock("CallCardPresenter.onDialerCallSessionModificationStateChange");
Eric Erfanianccca3152017-02-22 16:32:36 -0800410
411 if (mPrimary == null) {
412 return;
413 }
414 getUi()
415 .setEndCallButtonEnabled(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700416 mPrimary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700417 != SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800418 true /* shouldAnimate */);
419 updatePrimaryCallState();
420 }
421
Eric Erfanianccca3152017-02-22 16:32:36 -0800422 private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
423 if (mPrimary == null) {
424 return false;
425 }
426 return primaryChanged
427 || mInCallScreen.isManageConferenceVisible() != shouldShowManageConference();
428 }
429
430 private void updatePrimaryCallState() {
431 if (getUi() != null && mPrimary != null) {
432 boolean isWorkCall =
433 mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL)
434 || (mPrimaryContactInfo != null
435 && mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
436 boolean isHdAudioCall =
437 isPrimaryCallActive() && mPrimary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700438 boolean isAttemptingHdAudioCall =
439 !isHdAudioCall
440 && !mPrimary.hasProperty(DialerCall.PROPERTY_CODEC_KNOWN)
441 && MotorolaUtils.shouldBlinkHdIconWhenConnectingCall(mContext);
442
443 boolean isBusiness = mPrimaryContactInfo != null && mPrimaryContactInfo.isBusiness;
444
Eric Erfanianccca3152017-02-22 16:32:36 -0800445 // Check for video state change and update the visibility of the contact photo. The contact
446 // photo is hidden when the incoming video surface is shown.
447 // The contact photo visibility can also change in setPrimary().
448 boolean shouldShowContactPhoto =
449 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
450 getUi()
451 .setCallState(
452 new PrimaryCallState(
453 mPrimary.getState(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700454 mPrimary.isVideoCall(),
455 mPrimary.getVideoTech().getSessionModificationState(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800456 mPrimary.getDisconnectCause(),
457 getConnectionLabel(),
458 getCallStateIcon(),
459 getGatewayNumber(),
460 shouldShowCallSubject(mPrimary) ? mPrimary.getCallSubject() : null,
461 mPrimary.getCallbackNumber(),
462 mPrimary.hasProperty(Details.PROPERTY_WIFI),
Eric Erfanian83b20212017-05-31 08:53:10 -0700463 mPrimary.isConferenceCall()
464 && !mPrimary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE),
Eric Erfanianccca3152017-02-22 16:32:36 -0800465 isWorkCall,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700466 isAttemptingHdAudioCall,
Eric Erfanianccca3152017-02-22 16:32:36 -0800467 isHdAudioCall,
468 !TextUtils.isEmpty(mPrimary.getLastForwardedNumber()),
469 shouldShowContactPhoto,
470 mPrimary.getConnectTimeMillis(),
471 CallerInfoUtils.isVoiceMailNumber(mContext, mPrimary),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700472 mPrimary.isRemotelyHeld(),
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700473 isBusiness,
474 supports2ndCallOnHold()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800475
476 InCallActivity activity =
477 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
478 if (activity != null) {
479 activity.onPrimaryCallStateChanged();
480 }
481 }
482 }
483
484 /** Only show the conference call button if we can manage the conference. */
485 private void maybeShowManageConferenceCallButton() {
486 getUi().showManageConferenceCallButton(shouldShowManageConference());
487 }
488
489 /**
490 * Determines if the manage conference button should be visible, based on the current primary
491 * call.
492 *
493 * @return {@code True} if the manage conference button should be visible.
494 */
495 private boolean shouldShowManageConference() {
496 if (mPrimary == null) {
497 return false;
498 }
499
500 return mPrimary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)
501 && !mIsFullscreen;
502 }
503
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700504 private boolean supports2ndCallOnHold() {
505 DialerCall firstCall = CallList.getInstance().getActiveOrBackgroundCall();
506 DialerCall incomingCall = CallList.getInstance().getIncomingCall();
507 if (firstCall != null && incomingCall != null && firstCall != incomingCall) {
508 return incomingCall.can(Details.CAPABILITY_HOLD);
509 }
510 return true;
511 }
512
Eric Erfanianccca3152017-02-22 16:32:36 -0800513 @Override
514 public void onCallStateButtonClicked() {
515 Intent broadcastIntent = Bindings.get(mContext).getCallStateButtonBroadcastIntent(mContext);
516 if (broadcastIntent != null) {
517 LogUtil.v(
518 "CallCardPresenter.onCallStateButtonClicked",
519 "sending call state button broadcast: " + broadcastIntent);
520 mContext.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
521 }
522 }
523
524 @Override
525 public void onManageConferenceClicked() {
526 InCallActivity activity =
527 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
528 activity.showConferenceFragment(true);
529 }
530
531 @Override
532 public void onShrinkAnimationComplete() {
533 InCallPresenter.getInstance().onShrinkAnimationComplete();
534 }
535
536 @Override
537 public Drawable getDefaultContactPhotoDrawable() {
538 return ContactInfoCache.getInstance(mContext).getDefaultContactPhotoDrawable();
539 }
540
541 private void maybeStartSearch(DialerCall call, boolean isPrimary) {
542 // no need to start search for conference calls which show generic info.
543 if (call != null && !call.isConferenceCall()) {
544 startContactInfoSearch(call, isPrimary, call.getState() == DialerCall.State.INCOMING);
545 }
546 }
547
Eric Erfanianccca3152017-02-22 16:32:36 -0800548 /** Starts a query for more contact data for the save primary and secondary calls. */
549 private void startContactInfoSearch(
550 final DialerCall call, final boolean isPrimary, boolean isIncoming) {
551 final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
552
553 cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
554 }
555
556 private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
557 final boolean entryMatchesExistingCall =
558 (isPrimary && mPrimary != null && TextUtils.equals(callId, mPrimary.getId()))
559 || (!isPrimary && mSecondary != null && TextUtils.equals(callId, mSecondary.getId()));
560 if (entryMatchesExistingCall) {
561 updateContactEntry(entry, isPrimary);
562 } else {
563 LogUtil.e(
564 "CallCardPresenter.onContactInfoComplete",
565 "dropping stale contact lookup info for " + callId);
566 }
567
568 final DialerCall call = CallList.getInstance().getCallById(callId);
569 if (call != null) {
570 call.getLogState().contactLookupResult = entry.contactLookupResult;
571 }
572 if (entry.contactUri != null) {
573 CallerInfoUtils.sendViewNotification(mContext, entry.contactUri);
574 }
575 }
576
577 private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
578 if (getUi() == null) {
579 return;
580 }
581
582 if (entry.photo != null) {
583 if (mPrimary != null && callId.equals(mPrimary.getId())) {
584 updateContactEntry(entry, true /* isPrimary */);
585 } else if (mSecondary != null && callId.equals(mSecondary.getId())) {
586 updateContactEntry(entry, false /* isPrimary */);
587 }
588 }
589 }
590
591 private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
592 if (isPrimary) {
593 mPrimaryContactInfo = entry;
594 updatePrimaryDisplayInfo();
595 } else {
596 mSecondaryContactInfo = entry;
597 updateSecondaryDisplayInfo();
598 }
599 }
600
601 /**
602 * Get the highest priority call to display. Goes through the calls and chooses which to return
603 * based on priority of which type of call to display to the user. Callers can use the "ignore"
604 * feature to get the second best call by passing a previously found primary call as ignore.
605 *
606 * @param ignore A call to ignore if found.
607 */
608 private DialerCall getCallToDisplay(
609 CallList callList, DialerCall ignore, boolean skipDisconnected) {
610 // Active calls come second. An active call always gets precedent.
611 DialerCall retval = callList.getActiveCall();
612 if (retval != null && retval != ignore) {
613 return retval;
614 }
615
616 // Sometimes there is intemediate state that two calls are in active even one is about
617 // to be on hold.
618 retval = callList.getSecondActiveCall();
619 if (retval != null && retval != ignore) {
620 return retval;
621 }
622
623 // Disconnected calls get primary position if there are no active calls
624 // to let user know quickly what call has disconnected. Disconnected
625 // calls are very short lived.
626 if (!skipDisconnected) {
627 retval = callList.getDisconnectingCall();
628 if (retval != null && retval != ignore) {
629 return retval;
630 }
631 retval = callList.getDisconnectedCall();
632 if (retval != null && retval != ignore) {
633 return retval;
634 }
635 }
636
637 // Then we go to background call (calls on hold)
638 retval = callList.getBackgroundCall();
639 if (retval != null && retval != ignore) {
640 return retval;
641 }
642
643 // Lastly, we go to a second background call.
644 retval = callList.getSecondBackgroundCall();
645
646 return retval;
647 }
648
649 private void updatePrimaryDisplayInfo() {
650 if (mInCallScreen == null) {
651 // TODO: May also occur if search result comes back after ui is destroyed. Look into
652 // removing that case completely.
653 LogUtil.v(
654 "CallCardPresenter.updatePrimaryDisplayInfo",
655 "updatePrimaryDisplayInfo called but ui is null!");
656 return;
657 }
658
659 if (mPrimary == null) {
660 // Clear the primary display info.
661 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
662 return;
663 }
664
665 // Hide the contact photo if we are in a video call and the incoming video surface is
666 // showing.
667 boolean showContactPhoto =
668 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
669
670 // DialerCall placed through a work phone account.
671 boolean hasWorkCallProperty = mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL);
672
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700673 MultimediaData multimediaData = null;
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700674 if (mPrimary.getEnrichedCallSession() != null) {
675 multimediaData = mPrimary.getEnrichedCallSession().getMultimediaData();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700676 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800677
678 if (mPrimary.isConferenceCall()) {
679 LogUtil.v(
680 "CallCardPresenter.updatePrimaryDisplayInfo",
681 "update primary display info for conference call.");
682
683 mInCallScreen.setPrimary(
684 new PrimaryInfo(
685 null /* number */,
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700686 CallerInfoUtils.getConferenceString(
687 mContext, mPrimary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800688 false /* nameIsNumber */,
689 null /* location */,
690 null /* label */,
Eric Erfanian83b20212017-05-31 08:53:10 -0700691 null /* photo */,
Eric Erfanianccca3152017-02-22 16:32:36 -0800692 ContactPhotoType.DEFAULT_PLACEHOLDER,
693 false /* isSipCall */,
694 showContactPhoto,
695 hasWorkCallProperty,
696 false /* isSpam */,
697 false /* answeringDisconnectsOngoingCall */,
698 shouldShowLocation(),
699 null /* contactInfoLookupKey */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700700 null /* enrichedCallMultimediaData */,
701 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800702 } else if (mPrimaryContactInfo != null) {
703 LogUtil.v(
704 "CallCardPresenter.updatePrimaryDisplayInfo",
705 "update primary display info for " + mPrimaryContactInfo);
706
707 String name = getNameForCall(mPrimaryContactInfo);
708 String number;
709
710 boolean isChildNumberShown = !TextUtils.isEmpty(mPrimary.getChildNumber());
711 boolean isForwardedNumberShown = !TextUtils.isEmpty(mPrimary.getLastForwardedNumber());
712 boolean isCallSubjectShown = shouldShowCallSubject(mPrimary);
713
714 if (isCallSubjectShown) {
715 number = null;
716 } else if (isChildNumberShown) {
717 number = mContext.getString(R.string.child_number, mPrimary.getChildNumber());
718 } else if (isForwardedNumberShown) {
719 // Use last forwarded number instead of second line, if present.
720 number = mPrimary.getLastForwardedNumber();
721 } else {
722 number = mPrimaryContactInfo.number;
723 }
724
725 boolean nameIsNumber = name != null && name.equals(mPrimaryContactInfo.number);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700726
Eric Erfanianccca3152017-02-22 16:32:36 -0800727 // DialerCall with caller that is a work contact.
728 boolean isWorkContact = (mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
729 mInCallScreen.setPrimary(
730 new PrimaryInfo(
731 number,
Eric Erfanianc857f902017-05-15 14:05:33 -0700732 mPrimary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800733 nameIsNumber,
Eric Erfaniand8046e52017-04-06 09:41:50 -0700734 shouldShowLocationAsLabel(nameIsNumber, mPrimaryContactInfo.shouldShowLocation)
735 ? mPrimaryContactInfo.location
736 : null,
Eric Erfanianccca3152017-02-22 16:32:36 -0800737 isChildNumberShown || isCallSubjectShown ? null : mPrimaryContactInfo.label,
738 mPrimaryContactInfo.photo,
739 mPrimaryContactInfo.photoType,
740 mPrimaryContactInfo.isSipCall,
741 showContactPhoto,
742 hasWorkCallProperty || isWorkContact,
743 mPrimary.isSpam(),
744 mPrimary.answeringDisconnectsForegroundVideoCall(),
745 shouldShowLocation(),
746 mPrimaryContactInfo.lookupKey,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700747 multimediaData,
748 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800749 } else {
750 // Clear the primary display info.
751 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
752 }
753
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700754 if (isInCallScreenReady) {
755 mInCallScreen.showLocationUi(getLocationFragment());
756 } else {
757 LogUtil.i("CallCardPresenter.updatePrimaryDisplayInfo", "UI not ready, not showing location");
758 }
759 }
760
Eric Erfaniand8046e52017-04-06 09:41:50 -0700761 private static boolean shouldShowLocationAsLabel(
762 boolean nameIsNumber, boolean shouldShowLocation) {
763 if (nameIsNumber) {
764 return true;
765 }
766 if (shouldShowLocation) {
767 return true;
768 }
769 return false;
770 }
771
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700772 private Fragment getLocationFragment() {
773 if (!ConfigProviderBindings.get(mContext)
774 .getBoolean(CONFIG_ENABLE_EMERGENCY_LOCATION, CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT)) {
775 LogUtil.i("CallCardPresenter.getLocationFragment", "disabled by config.");
776 return null;
777 }
778 if (!shouldShowLocation()) {
779 LogUtil.i("CallCardPresenter.getLocationFragment", "shouldn't show location");
780 return null;
781 }
782 if (!hasLocationPermission()) {
783 LogUtil.i("CallCardPresenter.getLocationFragment", "no location permission.");
784 return null;
785 }
786 if (isBatteryTooLowForEmergencyLocation()) {
787 LogUtil.i("CallCardPresenter.getLocationFragment", "low battery.");
788 return null;
789 }
790 if (ActivityCompat.isInMultiWindowMode(mInCallScreen.getInCallScreenFragment().getActivity())) {
791 LogUtil.i("CallCardPresenter.getLocationFragment", "in multi-window mode");
792 return null;
793 }
794 if (mPrimary.isVideoCall()) {
795 LogUtil.i("CallCardPresenter.getLocationFragment", "emergency video calls not supported");
796 return null;
797 }
798 if (!callLocation.canGetLocation(mContext)) {
799 LogUtil.i("CallCardPresenter.getLocationFragment", "can't get current location");
800 return null;
801 }
802 LogUtil.i("CallCardPresenter.getLocationFragment", "returning location fragment");
803 return callLocation.getLocationFragment(mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -0800804 }
805
806 private boolean shouldShowLocation() {
807 if (isOutgoingEmergencyCall(mPrimary)) {
808 LogUtil.i("CallCardPresenter.shouldShowLocation", "new emergency call");
809 return true;
810 } else if (isIncomingEmergencyCall(mPrimary)) {
811 LogUtil.i("CallCardPresenter.shouldShowLocation", "potential emergency callback");
812 return true;
813 } else if (isIncomingEmergencyCall(mSecondary)) {
814 LogUtil.i("CallCardPresenter.shouldShowLocation", "has potential emergency callback");
815 return true;
816 }
817 return false;
818 }
819
820 private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
821 return call != null && !call.isIncoming() && call.isEmergencyCall();
822 }
823
824 private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
825 return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
826 }
827
828 private boolean hasLocationPermission() {
829 return ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION)
830 == PackageManager.PERMISSION_GRANTED;
831 }
832
833 private boolean isBatteryTooLowForEmergencyLocation() {
834 Intent batteryStatus =
835 mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
836 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
837 if (status == BatteryManager.BATTERY_STATUS_CHARGING
838 || status == BatteryManager.BATTERY_STATUS_FULL) {
839 // Plugged in or full battery
840 return false;
841 }
842 int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
843 int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
844 float batteryPercent = (100f * level) / scale;
845 long threshold =
846 ConfigProviderBindings.get(mContext)
847 .getLong(
848 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION,
849 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT);
850 LogUtil.i(
851 "CallCardPresenter.isBatteryTooLowForEmergencyLocation",
852 "percent charged: " + batteryPercent + ", min required charge: " + threshold);
853 return batteryPercent < threshold;
854 }
855
856 private void updateSecondaryDisplayInfo() {
857 if (mInCallScreen == null) {
858 return;
859 }
860
861 if (mSecondary == null) {
862 // Clear the secondary display info.
863 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
864 return;
865 }
866
867 if (mSecondary.isConferenceCall()) {
868 mInCallScreen.setSecondary(
869 new SecondaryInfo(
870 true /* show */,
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700871 CallerInfoUtils.getConferenceString(
872 mContext, mSecondary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800873 false /* nameIsNumber */,
874 null /* label */,
875 mSecondary.getCallProviderLabel(),
876 true /* isConference */,
877 mSecondary.isVideoCall(),
878 mIsFullscreen));
879 } else if (mSecondaryContactInfo != null) {
880 LogUtil.v("CallCardPresenter.updateSecondaryDisplayInfo", "" + mSecondaryContactInfo);
881 String name = getNameForCall(mSecondaryContactInfo);
882 boolean nameIsNumber = name != null && name.equals(mSecondaryContactInfo.number);
883 mInCallScreen.setSecondary(
884 new SecondaryInfo(
885 true /* show */,
Eric Erfanianc857f902017-05-15 14:05:33 -0700886 mSecondary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800887 nameIsNumber,
888 mSecondaryContactInfo.label,
889 mSecondary.getCallProviderLabel(),
890 false /* isConference */,
891 mSecondary.isVideoCall(),
892 mIsFullscreen));
893 } else {
894 // Clear the secondary display info.
895 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
896 }
897 }
898
899 /** Returns the gateway number for any existing outgoing call. */
900 private String getGatewayNumber() {
901 if (hasOutgoingGatewayCall()) {
902 return DialerCall.getNumberFromHandle(mPrimary.getGatewayInfo().getGatewayAddress());
903 }
904 return null;
905 }
906
907 /**
908 * Returns the label (line of text above the number/name) for any given call. For example,
909 * "calling via [Account/Google Voice]" for outgoing calls.
910 */
911 private String getConnectionLabel() {
912 if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_PHONE_STATE)
913 != PackageManager.PERMISSION_GRANTED) {
914 return null;
915 }
916 StatusHints statusHints = mPrimary.getStatusHints();
917 if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
918 return statusHints.getLabel().toString();
919 }
920
921 if (hasOutgoingGatewayCall() && getUi() != null) {
922 // Return the label for the gateway app on outgoing calls.
923 final PackageManager pm = mContext.getPackageManager();
924 try {
925 ApplicationInfo info =
926 pm.getApplicationInfo(mPrimary.getGatewayInfo().getGatewayProviderPackageName(), 0);
927 return pm.getApplicationLabel(info).toString();
928 } catch (PackageManager.NameNotFoundException e) {
929 LogUtil.e("CallCardPresenter.getConnectionLabel", "gateway Application Not Found.", e);
930 return null;
931 }
932 }
933 return mPrimary.getCallProviderLabel();
934 }
935
936 private Drawable getCallStateIcon() {
937 // Return connection icon if one exists.
938 StatusHints statusHints = mPrimary.getStatusHints();
939 if (statusHints != null && statusHints.getIcon() != null) {
940 Drawable icon = statusHints.getIcon().loadDrawable(mContext);
941 if (icon != null) {
942 return icon;
943 }
944 }
945
946 return null;
947 }
948
949 private boolean hasOutgoingGatewayCall() {
950 // We only display the gateway information while STATE_DIALING so return false for any other
951 // call state.
952 // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
953 // is also called after a contact search completes (call is not present yet). Split the
954 // UI update so it can receive independent updates.
955 if (mPrimary == null) {
956 return false;
957 }
958 return DialerCall.State.isDialing(mPrimary.getState())
959 && mPrimary.getGatewayInfo() != null
960 && !mPrimary.getGatewayInfo().isEmpty();
961 }
962
963 /** Gets the name to display for the call. */
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700964 private String getNameForCall(ContactCacheEntry contactInfo) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800965 String preferredName =
966 ContactDisplayUtils.getPreferredDisplayName(
967 contactInfo.namePrimary, contactInfo.nameAlternative, mContactsPreferences);
968 if (TextUtils.isEmpty(preferredName)) {
969 return contactInfo.number;
970 }
971 return preferredName;
972 }
973
Eric Erfanianccca3152017-02-22 16:32:36 -0800974 @Override
975 public void onSecondaryInfoClicked() {
976 if (mSecondary == null) {
977 LogUtil.e(
978 "CallCardPresenter.onSecondaryInfoClicked",
979 "secondary info clicked but no secondary call.");
980 return;
981 }
982
983 LogUtil.i(
984 "CallCardPresenter.onSecondaryInfoClicked", "swapping call to foreground: " + mSecondary);
985 mSecondary.unhold();
986 }
987
988 @Override
989 public void onEndCallClicked() {
990 LogUtil.i("CallCardPresenter.onEndCallClicked", "disconnecting call: " + mPrimary);
991 if (mPrimary != null) {
992 mPrimary.disconnect();
993 }
994 }
995
996 /**
997 * Handles a change to the fullscreen mode of the in-call UI.
998 *
999 * @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
1000 */
1001 @Override
1002 public void onFullscreenModeChanged(boolean isFullscreenMode) {
1003 mIsFullscreen = isFullscreenMode;
1004 if (mInCallScreen == null) {
1005 return;
1006 }
1007 maybeShowManageConferenceCallButton();
1008 }
1009
1010 private boolean isPrimaryCallActive() {
1011 return mPrimary != null && mPrimary.getState() == DialerCall.State.ACTIVE;
1012 }
1013
Eric Erfanianccca3152017-02-22 16:32:36 -08001014 private boolean shouldShowEndCallButton(DialerCall primary, int callState) {
1015 if (primary == null) {
1016 return false;
1017 }
1018 if ((!DialerCall.State.isConnectingOrConnected(callState)
1019 && callState != DialerCall.State.DISCONNECTING
1020 && callState != DialerCall.State.DISCONNECTED)
1021 || callState == DialerCall.State.INCOMING) {
1022 return false;
1023 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001024 if (mPrimary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -07001025 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001026 return false;
1027 }
1028 return true;
1029 }
1030
1031 @Override
1032 public void onInCallScreenResumed() {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001033 updatePrimaryDisplayInfo();
1034
Eric Erfanianccca3152017-02-22 16:32:36 -08001035 if (shouldSendAccessibilityEvent) {
1036 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1037 }
1038 }
1039
Eric Erfaniand8046e52017-04-06 09:41:50 -07001040 @Override
Eric Erfanian91ce7d22017-06-05 13:35:02 -07001041 public void onInCallScreenPaused() {}
Eric Erfaniand8046e52017-04-06 09:41:50 -07001042
Eric Erfanianccca3152017-02-22 16:32:36 -08001043 static boolean sendAccessibilityEvent(Context context, InCallScreen inCallScreen) {
1044 AccessibilityManager am =
1045 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
1046 if (!am.isEnabled()) {
1047 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "accessibility is off");
1048 return false;
1049 }
1050 if (inCallScreen == null) {
1051 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "incallscreen is null");
1052 return false;
1053 }
1054 Fragment fragment = inCallScreen.getInCallScreenFragment();
1055 if (fragment == null || fragment.getView() == null || fragment.getView().getParent() == null) {
1056 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "fragment/view/parent is null");
1057 return false;
1058 }
1059
1060 DisplayManager displayManager =
1061 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1062 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
1063 boolean screenIsOn = display.getState() == Display.STATE_ON;
1064 LogUtil.d("CallCardPresenter.sendAccessibilityEvent", "screen is on: %b", screenIsOn);
1065 if (!screenIsOn) {
1066 return false;
1067 }
1068
1069 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
1070 inCallScreen.dispatchPopulateAccessibilityEvent(event);
1071 View view = inCallScreen.getInCallScreenFragment().getView();
1072 view.getParent().requestSendAccessibilityEvent(view, event);
1073 return true;
1074 }
1075
1076 private void maybeSendAccessibilityEvent(
1077 InCallState oldState, final InCallState newState, boolean primaryChanged) {
1078 shouldSendAccessibilityEvent = false;
1079 if (mContext == null) {
1080 return;
1081 }
1082 final AccessibilityManager am =
1083 (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
1084 if (!am.isEnabled()) {
1085 return;
1086 }
1087 // Announce the current call if it's new incoming/outgoing call or primary call is changed
1088 // due to switching calls between two ongoing calls (one is on hold).
1089 if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
1090 || (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
1091 || primaryChanged) {
1092 LogUtil.i(
1093 "CallCardPresenter.maybeSendAccessibilityEvent", "schedule accessibility announcement");
1094 shouldSendAccessibilityEvent = true;
1095 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1096 }
1097 }
1098
1099 /**
1100 * Determines whether the call subject should be visible on the UI. For the call subject to be
1101 * visible, the call has to be in an incoming or waiting state, and the subject must not be empty.
1102 *
1103 * @param call The call.
1104 * @return {@code true} if the subject should be shown, {@code false} otherwise.
1105 */
1106 private boolean shouldShowCallSubject(DialerCall call) {
1107 if (call == null) {
1108 return false;
1109 }
1110
1111 boolean isIncomingOrWaiting =
1112 mPrimary.getState() == DialerCall.State.INCOMING
1113 || mPrimary.getState() == DialerCall.State.CALL_WAITING;
1114 return isIncomingOrWaiting
1115 && !TextUtils.isEmpty(call.getCallSubject())
1116 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED
1117 && call.isCallSubjectSupported();
1118 }
1119
1120 /**
1121 * Determines whether the "note sent" toast should be shown. It should be shown for a new outgoing
1122 * call with a subject.
1123 *
1124 * @param call The call
1125 * @return {@code true} if the toast should be shown, {@code false} otherwise.
1126 */
1127 private boolean shouldShowNoteSentToast(DialerCall call) {
1128 return call != null
1129 && hasCallSubject(call)
1130 && (call.getState() == DialerCall.State.DIALING
1131 || call.getState() == DialerCall.State.CONNECTING);
1132 }
1133
1134 private InCallScreen getUi() {
1135 return mInCallScreen;
1136 }
1137
1138 public static class ContactLookupCallback implements ContactInfoCacheCallback {
1139
1140 private final WeakReference<CallCardPresenter> mCallCardPresenter;
1141 private final boolean mIsPrimary;
1142
1143 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
1144 mCallCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
1145 mIsPrimary = isPrimary;
1146 }
1147
1148 @Override
1149 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
1150 CallCardPresenter presenter = mCallCardPresenter.get();
1151 if (presenter != null) {
1152 presenter.onContactInfoComplete(callId, entry, mIsPrimary);
1153 }
1154 }
1155
1156 @Override
1157 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
1158 CallCardPresenter presenter = mCallCardPresenter.get();
1159 if (presenter != null) {
1160 presenter.onImageLoadComplete(callId, entry);
1161 }
1162 }
1163 }
1164}