blob: 0b3592d01bdd59d3a9d4acec3d2701593100c067 [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;
50import com.android.dialer.enrichedcall.EnrichedCallComponent;
Eric Erfanianccca3152017-02-22 16:32:36 -080051import com.android.dialer.enrichedcall.EnrichedCallManager;
52import com.android.dialer.enrichedcall.Session;
Eric Erfanian8369df02017-05-03 10:27:13 -070053import com.android.dialer.logging.DialerImpression;
Eric Erfaniand8046e52017-04-06 09:41:50 -070054import com.android.dialer.logging.Logger;
Eric Erfanianccca3152017-02-22 16:32:36 -080055import com.android.dialer.multimedia.MultimediaData;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070056import com.android.dialer.oem.MotorolaUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -080057import com.android.incallui.ContactInfoCache.ContactCacheEntry;
58import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
59import com.android.incallui.InCallPresenter.InCallDetailsListener;
60import com.android.incallui.InCallPresenter.InCallEventListener;
61import com.android.incallui.InCallPresenter.InCallState;
62import com.android.incallui.InCallPresenter.InCallStateListener;
63import com.android.incallui.InCallPresenter.IncomingCallListener;
64import com.android.incallui.call.CallList;
65import com.android.incallui.call.DialerCall;
Eric 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;
73import com.android.incallui.incall.protocol.PrimaryInfo;
74import com.android.incallui.incall.protocol.SecondaryInfo;
Eric Erfanian90508232017-03-24 09:31:16 -070075import com.android.incallui.videotech.utils.SessionModificationState;
Eric Erfanianccca3152017-02-22 16:32:36 -080076import java.lang.ref.WeakReference;
77
78/**
79 * Controller for the Call Card Fragment. This class listens for changes to InCallState and passes
80 * it along to the fragment.
81 */
82public class CallCardPresenter
83 implements InCallStateListener,
84 IncomingCallListener,
85 InCallDetailsListener,
86 InCallEventListener,
87 InCallScreenDelegate,
88 DialerCallListener,
89 EnrichedCallManager.StateChangedListener {
90
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) {
252 LogUtil.v("CallCardPresenter.onStateChange", "" + newState);
253 if (mInCallScreen == null) {
254 return;
255 }
256
257 DialerCall primary = null;
258 DialerCall secondary = null;
259
260 if (newState == InCallState.INCOMING) {
261 primary = callList.getIncomingCall();
262 } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) {
263 primary = callList.getOutgoingCall();
264 if (primary == null) {
265 primary = callList.getPendingOutgoingCall();
266 }
267
268 // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the
269 // highest priority call to display as the secondary call.
270 secondary = getCallToDisplay(callList, null, true);
271 } else if (newState == InCallState.INCALL) {
272 primary = getCallToDisplay(callList, null, false);
273 secondary = getCallToDisplay(callList, primary, true);
274 }
275
276 LogUtil.v("CallCardPresenter.onStateChange", "primary call: " + primary);
277 LogUtil.v("CallCardPresenter.onStateChange", "secondary call: " + secondary);
278
279 final boolean primaryChanged =
280 !(DialerCall.areSame(mPrimary, primary) && DialerCall.areSameNumber(mPrimary, primary));
281 final boolean secondaryChanged =
282 !(DialerCall.areSame(mSecondary, secondary)
283 && DialerCall.areSameNumber(mSecondary, secondary));
284
285 mSecondary = secondary;
286 DialerCall previousPrimary = mPrimary;
287 mPrimary = primary;
288
289 if (mPrimary != null) {
290 InCallPresenter.getInstance().onForegroundCallChanged(mPrimary);
291 mInCallScreen.updateInCallScreenColors();
292 }
293
294 if (primaryChanged && shouldShowNoteSentToast(primary)) {
295 mInCallScreen.showNoteSentToast();
296 }
297
298 // Refresh primary call information if either:
299 // 1. Primary call changed.
300 // 2. The call's ability to manage conference has changed.
301 if (shouldRefreshPrimaryInfo(primaryChanged)) {
302 // primary call has changed
303 if (previousPrimary != null) {
304 previousPrimary.removeListener(this);
305 }
306 mPrimary.addListener(this);
307
308 mPrimaryContactInfo =
309 ContactInfoCache.buildCacheEntryFromCall(
310 mContext, mPrimary, mPrimary.getState() == DialerCall.State.INCOMING);
311 updatePrimaryDisplayInfo();
312 maybeStartSearch(mPrimary, true);
Eric Erfanianccca3152017-02-22 16:32:36 -0800313 }
314
315 if (previousPrimary != null && mPrimary == null) {
316 previousPrimary.removeListener(this);
317 }
318
319 if (mSecondary == null) {
320 // Secondary call may have ended. Update the ui.
321 mSecondaryContactInfo = null;
322 updateSecondaryDisplayInfo();
323 } else if (secondaryChanged) {
324 // secondary call has changed
325 mSecondaryContactInfo =
326 ContactInfoCache.buildCacheEntryFromCall(
327 mContext, mSecondary, mSecondary.getState() == DialerCall.State.INCOMING);
328 updateSecondaryDisplayInfo();
329 maybeStartSearch(mSecondary, false);
Eric Erfanianccca3152017-02-22 16:32:36 -0800330 }
331
332 // Set the call state
333 int callState = DialerCall.State.IDLE;
334 if (mPrimary != null) {
335 callState = mPrimary.getState();
336 updatePrimaryCallState();
337 } else {
338 getUi().setCallState(PrimaryCallState.createEmptyPrimaryCallState());
339 }
340
341 maybeShowManageConferenceCallButton();
342
343 // Hide the end call button instantly if we're receiving an incoming call.
344 getUi()
345 .setEndCallButtonEnabled(
346 shouldShowEndCallButton(mPrimary, callState),
347 callState != DialerCall.State.INCOMING /* animate */);
348
349 maybeSendAccessibilityEvent(oldState, newState, primaryChanged);
350 }
351
352 @Override
353 public void onDetailsChanged(DialerCall call, Details details) {
354 updatePrimaryCallState();
355
356 if (call.can(Details.CAPABILITY_MANAGE_CONFERENCE)
357 != details.can(Details.CAPABILITY_MANAGE_CONFERENCE)) {
358 maybeShowManageConferenceCallButton();
359 }
360 }
361
362 @Override
363 public void onDialerCallDisconnect() {}
364
365 @Override
366 public void onDialerCallUpdate() {
367 // No-op; specific call updates handled elsewhere.
368 }
369
370 @Override
371 public void onWiFiToLteHandover() {}
372
373 @Override
374 public void onHandoverToWifiFailure() {}
375
Eric Erfanianc857f902017-05-15 14:05:33 -0700376 @Override
377 public void onInternationalCallOnWifi() {}
378
Eric Erfanianccca3152017-02-22 16:32:36 -0800379 /** Handles a change to the child number by refreshing the primary call info. */
380 @Override
381 public void onDialerCallChildNumberChange() {
382 LogUtil.v("CallCardPresenter.onDialerCallChildNumberChange", "");
383
384 if (mPrimary == null) {
385 return;
386 }
387 updatePrimaryDisplayInfo();
388 }
389
390 /** Handles a change to the last forwarding number by refreshing the primary call info. */
391 @Override
392 public void onDialerCallLastForwardedNumberChange() {
393 LogUtil.v("CallCardPresenter.onDialerCallLastForwardedNumberChange", "");
394
395 if (mPrimary == null) {
396 return;
397 }
398 updatePrimaryDisplayInfo();
399 updatePrimaryCallState();
400 }
401
402 @Override
403 public void onDialerCallUpgradeToVideo() {}
404
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700405 /** Handles a change to the session modification state for a call. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800406 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700407 public void onDialerCallSessionModificationStateChange() {
408 LogUtil.enterBlock("CallCardPresenter.onDialerCallSessionModificationStateChange");
Eric Erfanianccca3152017-02-22 16:32:36 -0800409
410 if (mPrimary == null) {
411 return;
412 }
413 getUi()
414 .setEndCallButtonEnabled(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700415 mPrimary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700416 != SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800417 true /* shouldAnimate */);
418 updatePrimaryCallState();
419 }
420
421 @Override
422 public void onEnrichedCallStateChanged() {
423 LogUtil.enterBlock("CallCardPresenter.onEnrichedCallStateChanged");
424 updatePrimaryDisplayInfo();
425 }
426
427 private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
428 if (mPrimary == null) {
429 return false;
430 }
431 return primaryChanged
432 || mInCallScreen.isManageConferenceVisible() != shouldShowManageConference();
433 }
434
435 private void updatePrimaryCallState() {
436 if (getUi() != null && mPrimary != null) {
437 boolean isWorkCall =
438 mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL)
439 || (mPrimaryContactInfo != null
440 && mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
441 boolean isHdAudioCall =
442 isPrimaryCallActive() && mPrimary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700443 boolean isAttemptingHdAudioCall =
444 !isHdAudioCall
445 && !mPrimary.hasProperty(DialerCall.PROPERTY_CODEC_KNOWN)
446 && MotorolaUtils.shouldBlinkHdIconWhenConnectingCall(mContext);
447
448 boolean isBusiness = mPrimaryContactInfo != null && mPrimaryContactInfo.isBusiness;
449
Eric Erfanianccca3152017-02-22 16:32:36 -0800450 // Check for video state change and update the visibility of the contact photo. The contact
451 // photo is hidden when the incoming video surface is shown.
452 // The contact photo visibility can also change in setPrimary().
453 boolean shouldShowContactPhoto =
454 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
455 getUi()
456 .setCallState(
457 new PrimaryCallState(
458 mPrimary.getState(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700459 mPrimary.isVideoCall(),
460 mPrimary.getVideoTech().getSessionModificationState(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800461 mPrimary.getDisconnectCause(),
462 getConnectionLabel(),
463 getCallStateIcon(),
464 getGatewayNumber(),
465 shouldShowCallSubject(mPrimary) ? mPrimary.getCallSubject() : null,
466 mPrimary.getCallbackNumber(),
467 mPrimary.hasProperty(Details.PROPERTY_WIFI),
468 mPrimary.isConferenceCall(),
469 isWorkCall,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700470 isAttemptingHdAudioCall,
Eric Erfanianccca3152017-02-22 16:32:36 -0800471 isHdAudioCall,
472 !TextUtils.isEmpty(mPrimary.getLastForwardedNumber()),
473 shouldShowContactPhoto,
474 mPrimary.getConnectTimeMillis(),
475 CallerInfoUtils.isVoiceMailNumber(mContext, mPrimary),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700476 mPrimary.isRemotelyHeld(),
477 isBusiness));
Eric Erfanianccca3152017-02-22 16:32:36 -0800478
479 InCallActivity activity =
480 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
481 if (activity != null) {
482 activity.onPrimaryCallStateChanged();
483 }
484 }
485 }
486
487 /** Only show the conference call button if we can manage the conference. */
488 private void maybeShowManageConferenceCallButton() {
489 getUi().showManageConferenceCallButton(shouldShowManageConference());
490 }
491
492 /**
493 * Determines if the manage conference button should be visible, based on the current primary
494 * call.
495 *
496 * @return {@code True} if the manage conference button should be visible.
497 */
498 private boolean shouldShowManageConference() {
499 if (mPrimary == null) {
500 return false;
501 }
502
503 return mPrimary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)
504 && !mIsFullscreen;
505 }
506
507 @Override
508 public void onCallStateButtonClicked() {
509 Intent broadcastIntent = Bindings.get(mContext).getCallStateButtonBroadcastIntent(mContext);
510 if (broadcastIntent != null) {
511 LogUtil.v(
512 "CallCardPresenter.onCallStateButtonClicked",
513 "sending call state button broadcast: " + broadcastIntent);
514 mContext.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
515 }
516 }
517
518 @Override
519 public void onManageConferenceClicked() {
520 InCallActivity activity =
521 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
522 activity.showConferenceFragment(true);
523 }
524
525 @Override
526 public void onShrinkAnimationComplete() {
527 InCallPresenter.getInstance().onShrinkAnimationComplete();
528 }
529
530 @Override
531 public Drawable getDefaultContactPhotoDrawable() {
532 return ContactInfoCache.getInstance(mContext).getDefaultContactPhotoDrawable();
533 }
534
535 private void maybeStartSearch(DialerCall call, boolean isPrimary) {
536 // no need to start search for conference calls which show generic info.
537 if (call != null && !call.isConferenceCall()) {
538 startContactInfoSearch(call, isPrimary, call.getState() == DialerCall.State.INCOMING);
539 }
540 }
541
Eric Erfanianccca3152017-02-22 16:32:36 -0800542 /** Starts a query for more contact data for the save primary and secondary calls. */
543 private void startContactInfoSearch(
544 final DialerCall call, final boolean isPrimary, boolean isIncoming) {
545 final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
546
547 cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
548 }
549
550 private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
551 final boolean entryMatchesExistingCall =
552 (isPrimary && mPrimary != null && TextUtils.equals(callId, mPrimary.getId()))
553 || (!isPrimary && mSecondary != null && TextUtils.equals(callId, mSecondary.getId()));
554 if (entryMatchesExistingCall) {
555 updateContactEntry(entry, isPrimary);
556 } else {
557 LogUtil.e(
558 "CallCardPresenter.onContactInfoComplete",
559 "dropping stale contact lookup info for " + callId);
560 }
561
562 final DialerCall call = CallList.getInstance().getCallById(callId);
563 if (call != null) {
564 call.getLogState().contactLookupResult = entry.contactLookupResult;
565 }
566 if (entry.contactUri != null) {
567 CallerInfoUtils.sendViewNotification(mContext, entry.contactUri);
568 }
569 }
570
571 private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
572 if (getUi() == null) {
573 return;
574 }
575
576 if (entry.photo != null) {
577 if (mPrimary != null && callId.equals(mPrimary.getId())) {
578 updateContactEntry(entry, true /* isPrimary */);
579 } else if (mSecondary != null && callId.equals(mSecondary.getId())) {
580 updateContactEntry(entry, false /* isPrimary */);
581 }
582 }
583 }
584
585 private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
586 if (isPrimary) {
587 mPrimaryContactInfo = entry;
588 updatePrimaryDisplayInfo();
589 } else {
590 mSecondaryContactInfo = entry;
591 updateSecondaryDisplayInfo();
592 }
593 }
594
595 /**
596 * Get the highest priority call to display. Goes through the calls and chooses which to return
597 * based on priority of which type of call to display to the user. Callers can use the "ignore"
598 * feature to get the second best call by passing a previously found primary call as ignore.
599 *
600 * @param ignore A call to ignore if found.
601 */
602 private DialerCall getCallToDisplay(
603 CallList callList, DialerCall ignore, boolean skipDisconnected) {
604 // Active calls come second. An active call always gets precedent.
605 DialerCall retval = callList.getActiveCall();
606 if (retval != null && retval != ignore) {
607 return retval;
608 }
609
610 // Sometimes there is intemediate state that two calls are in active even one is about
611 // to be on hold.
612 retval = callList.getSecondActiveCall();
613 if (retval != null && retval != ignore) {
614 return retval;
615 }
616
617 // Disconnected calls get primary position if there are no active calls
618 // to let user know quickly what call has disconnected. Disconnected
619 // calls are very short lived.
620 if (!skipDisconnected) {
621 retval = callList.getDisconnectingCall();
622 if (retval != null && retval != ignore) {
623 return retval;
624 }
625 retval = callList.getDisconnectedCall();
626 if (retval != null && retval != ignore) {
627 return retval;
628 }
629 }
630
631 // Then we go to background call (calls on hold)
632 retval = callList.getBackgroundCall();
633 if (retval != null && retval != ignore) {
634 return retval;
635 }
636
637 // Lastly, we go to a second background call.
638 retval = callList.getSecondBackgroundCall();
639
640 return retval;
641 }
642
643 private void updatePrimaryDisplayInfo() {
644 if (mInCallScreen == null) {
645 // TODO: May also occur if search result comes back after ui is destroyed. Look into
646 // removing that case completely.
647 LogUtil.v(
648 "CallCardPresenter.updatePrimaryDisplayInfo",
649 "updatePrimaryDisplayInfo called but ui is null!");
650 return;
651 }
652
653 if (mPrimary == null) {
654 // Clear the primary display info.
655 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
656 return;
657 }
658
659 // Hide the contact photo if we are in a video call and the incoming video surface is
660 // showing.
661 boolean showContactPhoto =
662 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
663
664 // DialerCall placed through a work phone account.
665 boolean hasWorkCallProperty = mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL);
666
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700667 MultimediaData multimediaData = null;
668 if (mPrimary.getNumber() != null) {
Eric Erfanian8369df02017-05-03 10:27:13 -0700669 EnrichedCallManager manager = EnrichedCallComponent.get(mContext).getEnrichedCallManager();
Eric Erfanian10b34a52017-05-04 08:23:17 -0700670
671 EnrichedCallManager.Filter filter;
672 if (mPrimary.isIncoming()) {
673 filter = manager.createIncomingCallComposerFilter();
674 } else {
675 filter = manager.createOutgoingCallComposerFilter();
676 }
677
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700678 Session enrichedCallSession =
Eric Erfanian10b34a52017-05-04 08:23:17 -0700679 manager.getSession(mPrimary.getUniqueCallId(), mPrimary.getNumber(), filter);
Eric Erfanian8369df02017-05-03 10:27:13 -0700680
681 mPrimary.setEnrichedCallSession(enrichedCallSession);
682 mPrimary.setEnrichedCallCapabilities(manager.getCapabilities(mPrimary.getNumber()));
683
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700684 if (enrichedCallSession != null) {
685 enrichedCallSession.setUniqueDialerCallId(mPrimary.getUniqueCallId());
686 multimediaData = enrichedCallSession.getMultimediaData();
687 }
688 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800689
690 if (mPrimary.isConferenceCall()) {
691 LogUtil.v(
692 "CallCardPresenter.updatePrimaryDisplayInfo",
693 "update primary display info for conference call.");
694
695 mInCallScreen.setPrimary(
696 new PrimaryInfo(
697 null /* number */,
698 getConferenceString(mPrimary),
699 false /* nameIsNumber */,
700 null /* location */,
701 null /* label */,
702 getConferencePhoto(mPrimary),
703 ContactPhotoType.DEFAULT_PLACEHOLDER,
704 false /* isSipCall */,
705 showContactPhoto,
706 hasWorkCallProperty,
707 false /* isSpam */,
708 false /* answeringDisconnectsOngoingCall */,
709 shouldShowLocation(),
710 null /* contactInfoLookupKey */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700711 null /* enrichedCallMultimediaData */,
712 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800713 } else if (mPrimaryContactInfo != null) {
714 LogUtil.v(
715 "CallCardPresenter.updatePrimaryDisplayInfo",
716 "update primary display info for " + mPrimaryContactInfo);
717
718 String name = getNameForCall(mPrimaryContactInfo);
719 String number;
720
721 boolean isChildNumberShown = !TextUtils.isEmpty(mPrimary.getChildNumber());
722 boolean isForwardedNumberShown = !TextUtils.isEmpty(mPrimary.getLastForwardedNumber());
723 boolean isCallSubjectShown = shouldShowCallSubject(mPrimary);
724
725 if (isCallSubjectShown) {
726 number = null;
727 } else if (isChildNumberShown) {
728 number = mContext.getString(R.string.child_number, mPrimary.getChildNumber());
729 } else if (isForwardedNumberShown) {
730 // Use last forwarded number instead of second line, if present.
731 number = mPrimary.getLastForwardedNumber();
732 } else {
733 number = mPrimaryContactInfo.number;
734 }
735
736 boolean nameIsNumber = name != null && name.equals(mPrimaryContactInfo.number);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700737
Eric Erfanianccca3152017-02-22 16:32:36 -0800738 // DialerCall with caller that is a work contact.
739 boolean isWorkContact = (mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
740 mInCallScreen.setPrimary(
741 new PrimaryInfo(
742 number,
Eric Erfanianc857f902017-05-15 14:05:33 -0700743 mPrimary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800744 nameIsNumber,
Eric Erfaniand8046e52017-04-06 09:41:50 -0700745 shouldShowLocationAsLabel(nameIsNumber, mPrimaryContactInfo.shouldShowLocation)
746 ? mPrimaryContactInfo.location
747 : null,
Eric Erfanianccca3152017-02-22 16:32:36 -0800748 isChildNumberShown || isCallSubjectShown ? null : mPrimaryContactInfo.label,
749 mPrimaryContactInfo.photo,
750 mPrimaryContactInfo.photoType,
751 mPrimaryContactInfo.isSipCall,
752 showContactPhoto,
753 hasWorkCallProperty || isWorkContact,
754 mPrimary.isSpam(),
755 mPrimary.answeringDisconnectsForegroundVideoCall(),
756 shouldShowLocation(),
757 mPrimaryContactInfo.lookupKey,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700758 multimediaData,
759 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800760 } else {
761 // Clear the primary display info.
762 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
763 }
764
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700765 if (isInCallScreenReady) {
766 mInCallScreen.showLocationUi(getLocationFragment());
767 } else {
768 LogUtil.i("CallCardPresenter.updatePrimaryDisplayInfo", "UI not ready, not showing location");
769 }
770 }
771
Eric Erfaniand8046e52017-04-06 09:41:50 -0700772 private static boolean shouldShowLocationAsLabel(
773 boolean nameIsNumber, boolean shouldShowLocation) {
774 if (nameIsNumber) {
775 return true;
776 }
777 if (shouldShowLocation) {
778 return true;
779 }
780 return false;
781 }
782
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700783 private Fragment getLocationFragment() {
784 if (!ConfigProviderBindings.get(mContext)
785 .getBoolean(CONFIG_ENABLE_EMERGENCY_LOCATION, CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT)) {
786 LogUtil.i("CallCardPresenter.getLocationFragment", "disabled by config.");
787 return null;
788 }
789 if (!shouldShowLocation()) {
790 LogUtil.i("CallCardPresenter.getLocationFragment", "shouldn't show location");
791 return null;
792 }
793 if (!hasLocationPermission()) {
794 LogUtil.i("CallCardPresenter.getLocationFragment", "no location permission.");
795 return null;
796 }
797 if (isBatteryTooLowForEmergencyLocation()) {
798 LogUtil.i("CallCardPresenter.getLocationFragment", "low battery.");
799 return null;
800 }
801 if (ActivityCompat.isInMultiWindowMode(mInCallScreen.getInCallScreenFragment().getActivity())) {
802 LogUtil.i("CallCardPresenter.getLocationFragment", "in multi-window mode");
803 return null;
804 }
805 if (mPrimary.isVideoCall()) {
806 LogUtil.i("CallCardPresenter.getLocationFragment", "emergency video calls not supported");
807 return null;
808 }
809 if (!callLocation.canGetLocation(mContext)) {
810 LogUtil.i("CallCardPresenter.getLocationFragment", "can't get current location");
811 return null;
812 }
813 LogUtil.i("CallCardPresenter.getLocationFragment", "returning location fragment");
814 return callLocation.getLocationFragment(mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -0800815 }
816
817 private boolean shouldShowLocation() {
818 if (isOutgoingEmergencyCall(mPrimary)) {
819 LogUtil.i("CallCardPresenter.shouldShowLocation", "new emergency call");
820 return true;
821 } else if (isIncomingEmergencyCall(mPrimary)) {
822 LogUtil.i("CallCardPresenter.shouldShowLocation", "potential emergency callback");
823 return true;
824 } else if (isIncomingEmergencyCall(mSecondary)) {
825 LogUtil.i("CallCardPresenter.shouldShowLocation", "has potential emergency callback");
826 return true;
827 }
828 return false;
829 }
830
831 private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
832 return call != null && !call.isIncoming() && call.isEmergencyCall();
833 }
834
835 private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
836 return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
837 }
838
839 private boolean hasLocationPermission() {
840 return ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION)
841 == PackageManager.PERMISSION_GRANTED;
842 }
843
844 private boolean isBatteryTooLowForEmergencyLocation() {
845 Intent batteryStatus =
846 mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
847 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
848 if (status == BatteryManager.BATTERY_STATUS_CHARGING
849 || status == BatteryManager.BATTERY_STATUS_FULL) {
850 // Plugged in or full battery
851 return false;
852 }
853 int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
854 int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
855 float batteryPercent = (100f * level) / scale;
856 long threshold =
857 ConfigProviderBindings.get(mContext)
858 .getLong(
859 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION,
860 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT);
861 LogUtil.i(
862 "CallCardPresenter.isBatteryTooLowForEmergencyLocation",
863 "percent charged: " + batteryPercent + ", min required charge: " + threshold);
864 return batteryPercent < threshold;
865 }
866
867 private void updateSecondaryDisplayInfo() {
868 if (mInCallScreen == null) {
869 return;
870 }
871
872 if (mSecondary == null) {
873 // Clear the secondary display info.
874 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
875 return;
876 }
877
878 if (mSecondary.isConferenceCall()) {
879 mInCallScreen.setSecondary(
880 new SecondaryInfo(
881 true /* show */,
882 getConferenceString(mSecondary),
883 false /* nameIsNumber */,
884 null /* label */,
885 mSecondary.getCallProviderLabel(),
886 true /* isConference */,
887 mSecondary.isVideoCall(),
888 mIsFullscreen));
889 } else if (mSecondaryContactInfo != null) {
890 LogUtil.v("CallCardPresenter.updateSecondaryDisplayInfo", "" + mSecondaryContactInfo);
891 String name = getNameForCall(mSecondaryContactInfo);
892 boolean nameIsNumber = name != null && name.equals(mSecondaryContactInfo.number);
893 mInCallScreen.setSecondary(
894 new SecondaryInfo(
895 true /* show */,
Eric Erfanianc857f902017-05-15 14:05:33 -0700896 mSecondary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800897 nameIsNumber,
898 mSecondaryContactInfo.label,
899 mSecondary.getCallProviderLabel(),
900 false /* isConference */,
901 mSecondary.isVideoCall(),
902 mIsFullscreen));
903 } else {
904 // Clear the secondary display info.
905 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
906 }
907 }
908
909 /** Returns the gateway number for any existing outgoing call. */
910 private String getGatewayNumber() {
911 if (hasOutgoingGatewayCall()) {
912 return DialerCall.getNumberFromHandle(mPrimary.getGatewayInfo().getGatewayAddress());
913 }
914 return null;
915 }
916
917 /**
918 * Returns the label (line of text above the number/name) for any given call. For example,
919 * "calling via [Account/Google Voice]" for outgoing calls.
920 */
921 private String getConnectionLabel() {
922 if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_PHONE_STATE)
923 != PackageManager.PERMISSION_GRANTED) {
924 return null;
925 }
926 StatusHints statusHints = mPrimary.getStatusHints();
927 if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
928 return statusHints.getLabel().toString();
929 }
930
931 if (hasOutgoingGatewayCall() && getUi() != null) {
932 // Return the label for the gateway app on outgoing calls.
933 final PackageManager pm = mContext.getPackageManager();
934 try {
935 ApplicationInfo info =
936 pm.getApplicationInfo(mPrimary.getGatewayInfo().getGatewayProviderPackageName(), 0);
937 return pm.getApplicationLabel(info).toString();
938 } catch (PackageManager.NameNotFoundException e) {
939 LogUtil.e("CallCardPresenter.getConnectionLabel", "gateway Application Not Found.", e);
940 return null;
941 }
942 }
943 return mPrimary.getCallProviderLabel();
944 }
945
946 private Drawable getCallStateIcon() {
947 // Return connection icon if one exists.
948 StatusHints statusHints = mPrimary.getStatusHints();
949 if (statusHints != null && statusHints.getIcon() != null) {
950 Drawable icon = statusHints.getIcon().loadDrawable(mContext);
951 if (icon != null) {
952 return icon;
953 }
954 }
955
956 return null;
957 }
958
959 private boolean hasOutgoingGatewayCall() {
960 // We only display the gateway information while STATE_DIALING so return false for any other
961 // call state.
962 // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
963 // is also called after a contact search completes (call is not present yet). Split the
964 // UI update so it can receive independent updates.
965 if (mPrimary == null) {
966 return false;
967 }
968 return DialerCall.State.isDialing(mPrimary.getState())
969 && mPrimary.getGatewayInfo() != null
970 && !mPrimary.getGatewayInfo().isEmpty();
971 }
972
973 /** Gets the name to display for the call. */
974 String getNameForCall(ContactCacheEntry contactInfo) {
975 String preferredName =
976 ContactDisplayUtils.getPreferredDisplayName(
977 contactInfo.namePrimary, contactInfo.nameAlternative, mContactsPreferences);
978 if (TextUtils.isEmpty(preferredName)) {
979 return contactInfo.number;
980 }
981 return preferredName;
982 }
983
984 /** Gets the number to display for a call. */
985 String getNumberForCall(ContactCacheEntry contactInfo) {
986 // If the name is empty, we use the number for the name...so don't show a second
987 // number in the number field
988 String preferredName =
989 ContactDisplayUtils.getPreferredDisplayName(
990 contactInfo.namePrimary, contactInfo.nameAlternative, mContactsPreferences);
991 if (TextUtils.isEmpty(preferredName)) {
992 return contactInfo.location;
993 }
994 return contactInfo.number;
995 }
996
997 @Override
998 public void onSecondaryInfoClicked() {
999 if (mSecondary == null) {
1000 LogUtil.e(
1001 "CallCardPresenter.onSecondaryInfoClicked",
1002 "secondary info clicked but no secondary call.");
1003 return;
1004 }
1005
1006 LogUtil.i(
1007 "CallCardPresenter.onSecondaryInfoClicked", "swapping call to foreground: " + mSecondary);
1008 mSecondary.unhold();
1009 }
1010
1011 @Override
1012 public void onEndCallClicked() {
1013 LogUtil.i("CallCardPresenter.onEndCallClicked", "disconnecting call: " + mPrimary);
1014 if (mPrimary != null) {
1015 mPrimary.disconnect();
1016 }
1017 }
1018
1019 /**
1020 * Handles a change to the fullscreen mode of the in-call UI.
1021 *
1022 * @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
1023 */
1024 @Override
1025 public void onFullscreenModeChanged(boolean isFullscreenMode) {
1026 mIsFullscreen = isFullscreenMode;
1027 if (mInCallScreen == null) {
1028 return;
1029 }
1030 maybeShowManageConferenceCallButton();
1031 }
1032
1033 private boolean isPrimaryCallActive() {
1034 return mPrimary != null && mPrimary.getState() == DialerCall.State.ACTIVE;
1035 }
1036
1037 private String getConferenceString(DialerCall call) {
1038 boolean isGenericConference = call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE);
1039 LogUtil.v("CallCardPresenter.getConferenceString", "" + isGenericConference);
1040
1041 final int resId =
1042 isGenericConference ? R.string.generic_conference_call_name : R.string.conference_call_name;
1043 return mContext.getResources().getString(resId);
1044 }
1045
1046 private Drawable getConferencePhoto(DialerCall call) {
1047 boolean isGenericConference = call.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE);
1048 LogUtil.v("CallCardPresenter.getConferencePhoto", "" + isGenericConference);
1049
1050 final int resId = isGenericConference ? R.drawable.img_phone : R.drawable.img_conference;
1051 Drawable photo = mContext.getResources().getDrawable(resId);
1052 photo.setAutoMirrored(true);
1053 return photo;
1054 }
1055
1056 private boolean shouldShowEndCallButton(DialerCall primary, int callState) {
1057 if (primary == null) {
1058 return false;
1059 }
1060 if ((!DialerCall.State.isConnectingOrConnected(callState)
1061 && callState != DialerCall.State.DISCONNECTING
1062 && callState != DialerCall.State.DISCONNECTED)
1063 || callState == DialerCall.State.INCOMING) {
1064 return false;
1065 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001066 if (mPrimary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -07001067 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001068 return false;
1069 }
1070 return true;
1071 }
1072
1073 @Override
1074 public void onInCallScreenResumed() {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001075 EnrichedCallComponent.get(mContext).getEnrichedCallManager().registerStateChangedListener(this);
1076 updatePrimaryDisplayInfo();
1077
Eric Erfanianccca3152017-02-22 16:32:36 -08001078 if (shouldSendAccessibilityEvent) {
1079 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1080 }
1081 }
1082
Eric Erfaniand8046e52017-04-06 09:41:50 -07001083 @Override
1084 public void onInCallScreenPaused() {
1085 EnrichedCallComponent.get(mContext)
1086 .getEnrichedCallManager()
1087 .unregisterStateChangedListener(this);
1088 }
1089
Eric Erfanianccca3152017-02-22 16:32:36 -08001090 static boolean sendAccessibilityEvent(Context context, InCallScreen inCallScreen) {
1091 AccessibilityManager am =
1092 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
1093 if (!am.isEnabled()) {
1094 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "accessibility is off");
1095 return false;
1096 }
1097 if (inCallScreen == null) {
1098 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "incallscreen is null");
1099 return false;
1100 }
1101 Fragment fragment = inCallScreen.getInCallScreenFragment();
1102 if (fragment == null || fragment.getView() == null || fragment.getView().getParent() == null) {
1103 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "fragment/view/parent is null");
1104 return false;
1105 }
1106
1107 DisplayManager displayManager =
1108 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1109 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
1110 boolean screenIsOn = display.getState() == Display.STATE_ON;
1111 LogUtil.d("CallCardPresenter.sendAccessibilityEvent", "screen is on: %b", screenIsOn);
1112 if (!screenIsOn) {
1113 return false;
1114 }
1115
1116 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
1117 inCallScreen.dispatchPopulateAccessibilityEvent(event);
1118 View view = inCallScreen.getInCallScreenFragment().getView();
1119 view.getParent().requestSendAccessibilityEvent(view, event);
1120 return true;
1121 }
1122
1123 private void maybeSendAccessibilityEvent(
1124 InCallState oldState, final InCallState newState, boolean primaryChanged) {
1125 shouldSendAccessibilityEvent = false;
1126 if (mContext == null) {
1127 return;
1128 }
1129 final AccessibilityManager am =
1130 (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
1131 if (!am.isEnabled()) {
1132 return;
1133 }
1134 // Announce the current call if it's new incoming/outgoing call or primary call is changed
1135 // due to switching calls between two ongoing calls (one is on hold).
1136 if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
1137 || (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
1138 || primaryChanged) {
1139 LogUtil.i(
1140 "CallCardPresenter.maybeSendAccessibilityEvent", "schedule accessibility announcement");
1141 shouldSendAccessibilityEvent = true;
1142 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1143 }
1144 }
1145
1146 /**
1147 * Determines whether the call subject should be visible on the UI. For the call subject to be
1148 * visible, the call has to be in an incoming or waiting state, and the subject must not be empty.
1149 *
1150 * @param call The call.
1151 * @return {@code true} if the subject should be shown, {@code false} otherwise.
1152 */
1153 private boolean shouldShowCallSubject(DialerCall call) {
1154 if (call == null) {
1155 return false;
1156 }
1157
1158 boolean isIncomingOrWaiting =
1159 mPrimary.getState() == DialerCall.State.INCOMING
1160 || mPrimary.getState() == DialerCall.State.CALL_WAITING;
1161 return isIncomingOrWaiting
1162 && !TextUtils.isEmpty(call.getCallSubject())
1163 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED
1164 && call.isCallSubjectSupported();
1165 }
1166
1167 /**
1168 * Determines whether the "note sent" toast should be shown. It should be shown for a new outgoing
1169 * call with a subject.
1170 *
1171 * @param call The call
1172 * @return {@code true} if the toast should be shown, {@code false} otherwise.
1173 */
1174 private boolean shouldShowNoteSentToast(DialerCall call) {
1175 return call != null
1176 && hasCallSubject(call)
1177 && (call.getState() == DialerCall.State.DIALING
1178 || call.getState() == DialerCall.State.CONNECTING);
1179 }
1180
1181 private InCallScreen getUi() {
1182 return mInCallScreen;
1183 }
1184
1185 public static class ContactLookupCallback implements ContactInfoCacheCallback {
1186
1187 private final WeakReference<CallCardPresenter> mCallCardPresenter;
1188 private final boolean mIsPrimary;
1189
1190 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
1191 mCallCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
1192 mIsPrimary = isPrimary;
1193 }
1194
1195 @Override
1196 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
1197 CallCardPresenter presenter = mCallCardPresenter.get();
1198 if (presenter != null) {
1199 presenter.onContactInfoComplete(callId, entry, mIsPrimary);
1200 }
1201 }
1202
1203 @Override
1204 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
1205 CallCardPresenter presenter = mCallCardPresenter.get();
1206 if (presenter != null) {
1207 presenter.onImageLoadComplete(callId, entry);
1208 }
1209 }
1210 }
1211}