blob: 67b917767397015e47814c1475fc72d523d5a046 [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;
Eric Erfanianccca3152017-02-22 16:32:36 -080047import com.android.dialer.common.LogUtil;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070048import com.android.dialer.compat.ActivityCompat;
Eric Erfanianea7890c2017-06-19 12:40:59 -070049import com.android.dialer.configprovider.ConfigProviderBindings;
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) {
Eric Erfanianea7890c2017-06-19 12:40:59 -0700248 LogUtil.v("CallCardPresenter.onStateChange", "oldState: %s, newState: %s", oldState, newState);
Eric Erfanianccca3152017-02-22 16:32:36 -0800249 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() {
Eric Erfanianea7890c2017-06-19 12:40:59 -0700377 LogUtil.enterBlock("CallCardPresenter.onEnrichedCallSessionUpdate");
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700378 updatePrimaryDisplayInfo();
379 }
380
Eric Erfanianccca3152017-02-22 16:32:36 -0800381 /** Handles a change to the child number by refreshing the primary call info. */
382 @Override
383 public void onDialerCallChildNumberChange() {
384 LogUtil.v("CallCardPresenter.onDialerCallChildNumberChange", "");
385
386 if (mPrimary == null) {
387 return;
388 }
389 updatePrimaryDisplayInfo();
390 }
391
392 /** Handles a change to the last forwarding number by refreshing the primary call info. */
393 @Override
394 public void onDialerCallLastForwardedNumberChange() {
395 LogUtil.v("CallCardPresenter.onDialerCallLastForwardedNumberChange", "");
396
397 if (mPrimary == null) {
398 return;
399 }
400 updatePrimaryDisplayInfo();
401 updatePrimaryCallState();
402 }
403
404 @Override
405 public void onDialerCallUpgradeToVideo() {}
406
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700407 /** Handles a change to the session modification state for a call. */
Eric Erfanianccca3152017-02-22 16:32:36 -0800408 @Override
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700409 public void onDialerCallSessionModificationStateChange() {
410 LogUtil.enterBlock("CallCardPresenter.onDialerCallSessionModificationStateChange");
Eric Erfanianccca3152017-02-22 16:32:36 -0800411
412 if (mPrimary == null) {
413 return;
414 }
415 getUi()
416 .setEndCallButtonEnabled(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700417 mPrimary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -0700418 != SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST,
Eric Erfanianccca3152017-02-22 16:32:36 -0800419 true /* shouldAnimate */);
420 updatePrimaryCallState();
421 }
422
Eric Erfanianccca3152017-02-22 16:32:36 -0800423 private boolean shouldRefreshPrimaryInfo(boolean primaryChanged) {
424 if (mPrimary == null) {
425 return false;
426 }
427 return primaryChanged
428 || mInCallScreen.isManageConferenceVisible() != shouldShowManageConference();
429 }
430
431 private void updatePrimaryCallState() {
432 if (getUi() != null && mPrimary != null) {
433 boolean isWorkCall =
434 mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL)
435 || (mPrimaryContactInfo != null
436 && mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
437 boolean isHdAudioCall =
438 isPrimaryCallActive() && mPrimary.hasProperty(Details.PROPERTY_HIGH_DEF_AUDIO);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700439 boolean isAttemptingHdAudioCall =
440 !isHdAudioCall
441 && !mPrimary.hasProperty(DialerCall.PROPERTY_CODEC_KNOWN)
442 && MotorolaUtils.shouldBlinkHdIconWhenConnectingCall(mContext);
443
444 boolean isBusiness = mPrimaryContactInfo != null && mPrimaryContactInfo.isBusiness;
445
Eric Erfanianccca3152017-02-22 16:32:36 -0800446 // Check for video state change and update the visibility of the contact photo. The contact
447 // photo is hidden when the incoming video surface is shown.
448 // The contact photo visibility can also change in setPrimary().
449 boolean shouldShowContactPhoto =
450 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
451 getUi()
452 .setCallState(
453 new PrimaryCallState(
454 mPrimary.getState(),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700455 mPrimary.isVideoCall(),
456 mPrimary.getVideoTech().getSessionModificationState(),
Eric Erfanianccca3152017-02-22 16:32:36 -0800457 mPrimary.getDisconnectCause(),
458 getConnectionLabel(),
459 getCallStateIcon(),
460 getGatewayNumber(),
461 shouldShowCallSubject(mPrimary) ? mPrimary.getCallSubject() : null,
462 mPrimary.getCallbackNumber(),
463 mPrimary.hasProperty(Details.PROPERTY_WIFI),
Eric Erfanian83b20212017-05-31 08:53:10 -0700464 mPrimary.isConferenceCall()
465 && !mPrimary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE),
Eric Erfanianccca3152017-02-22 16:32:36 -0800466 isWorkCall,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700467 isAttemptingHdAudioCall,
Eric Erfanianccca3152017-02-22 16:32:36 -0800468 isHdAudioCall,
469 !TextUtils.isEmpty(mPrimary.getLastForwardedNumber()),
470 shouldShowContactPhoto,
471 mPrimary.getConnectTimeMillis(),
472 CallerInfoUtils.isVoiceMailNumber(mContext, mPrimary),
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700473 mPrimary.isRemotelyHeld(),
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700474 isBusiness,
475 supports2ndCallOnHold()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800476
477 InCallActivity activity =
478 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
479 if (activity != null) {
480 activity.onPrimaryCallStateChanged();
481 }
482 }
483 }
484
485 /** Only show the conference call button if we can manage the conference. */
486 private void maybeShowManageConferenceCallButton() {
487 getUi().showManageConferenceCallButton(shouldShowManageConference());
488 }
489
490 /**
491 * Determines if the manage conference button should be visible, based on the current primary
492 * call.
493 *
494 * @return {@code True} if the manage conference button should be visible.
495 */
496 private boolean shouldShowManageConference() {
497 if (mPrimary == null) {
498 return false;
499 }
500
501 return mPrimary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)
502 && !mIsFullscreen;
503 }
504
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700505 private boolean supports2ndCallOnHold() {
506 DialerCall firstCall = CallList.getInstance().getActiveOrBackgroundCall();
507 DialerCall incomingCall = CallList.getInstance().getIncomingCall();
508 if (firstCall != null && incomingCall != null && firstCall != incomingCall) {
509 return incomingCall.can(Details.CAPABILITY_HOLD);
510 }
511 return true;
512 }
513
Eric Erfanianccca3152017-02-22 16:32:36 -0800514 @Override
515 public void onCallStateButtonClicked() {
516 Intent broadcastIntent = Bindings.get(mContext).getCallStateButtonBroadcastIntent(mContext);
517 if (broadcastIntent != null) {
518 LogUtil.v(
519 "CallCardPresenter.onCallStateButtonClicked",
520 "sending call state button broadcast: " + broadcastIntent);
521 mContext.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE);
522 }
523 }
524
525 @Override
526 public void onManageConferenceClicked() {
527 InCallActivity activity =
528 (InCallActivity) (mInCallScreen.getInCallScreenFragment().getActivity());
529 activity.showConferenceFragment(true);
530 }
531
532 @Override
533 public void onShrinkAnimationComplete() {
534 InCallPresenter.getInstance().onShrinkAnimationComplete();
535 }
536
537 @Override
538 public Drawable getDefaultContactPhotoDrawable() {
539 return ContactInfoCache.getInstance(mContext).getDefaultContactPhotoDrawable();
540 }
541
542 private void maybeStartSearch(DialerCall call, boolean isPrimary) {
543 // no need to start search for conference calls which show generic info.
544 if (call != null && !call.isConferenceCall()) {
545 startContactInfoSearch(call, isPrimary, call.getState() == DialerCall.State.INCOMING);
546 }
547 }
548
Eric Erfanianccca3152017-02-22 16:32:36 -0800549 /** Starts a query for more contact data for the save primary and secondary calls. */
550 private void startContactInfoSearch(
551 final DialerCall call, final boolean isPrimary, boolean isIncoming) {
552 final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
553
554 cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary));
555 }
556
557 private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
558 final boolean entryMatchesExistingCall =
559 (isPrimary && mPrimary != null && TextUtils.equals(callId, mPrimary.getId()))
560 || (!isPrimary && mSecondary != null && TextUtils.equals(callId, mSecondary.getId()));
561 if (entryMatchesExistingCall) {
562 updateContactEntry(entry, isPrimary);
563 } else {
564 LogUtil.e(
565 "CallCardPresenter.onContactInfoComplete",
566 "dropping stale contact lookup info for " + callId);
567 }
568
569 final DialerCall call = CallList.getInstance().getCallById(callId);
570 if (call != null) {
571 call.getLogState().contactLookupResult = entry.contactLookupResult;
572 }
573 if (entry.contactUri != null) {
574 CallerInfoUtils.sendViewNotification(mContext, entry.contactUri);
575 }
576 }
577
578 private void onImageLoadComplete(String callId, ContactCacheEntry entry) {
579 if (getUi() == null) {
580 return;
581 }
582
583 if (entry.photo != null) {
584 if (mPrimary != null && callId.equals(mPrimary.getId())) {
585 updateContactEntry(entry, true /* isPrimary */);
586 } else if (mSecondary != null && callId.equals(mSecondary.getId())) {
587 updateContactEntry(entry, false /* isPrimary */);
588 }
589 }
590 }
591
592 private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
593 if (isPrimary) {
594 mPrimaryContactInfo = entry;
595 updatePrimaryDisplayInfo();
596 } else {
597 mSecondaryContactInfo = entry;
598 updateSecondaryDisplayInfo();
599 }
600 }
601
602 /**
603 * Get the highest priority call to display. Goes through the calls and chooses which to return
604 * based on priority of which type of call to display to the user. Callers can use the "ignore"
605 * feature to get the second best call by passing a previously found primary call as ignore.
606 *
607 * @param ignore A call to ignore if found.
608 */
609 private DialerCall getCallToDisplay(
610 CallList callList, DialerCall ignore, boolean skipDisconnected) {
611 // Active calls come second. An active call always gets precedent.
612 DialerCall retval = callList.getActiveCall();
613 if (retval != null && retval != ignore) {
614 return retval;
615 }
616
617 // Sometimes there is intemediate state that two calls are in active even one is about
618 // to be on hold.
619 retval = callList.getSecondActiveCall();
620 if (retval != null && retval != ignore) {
621 return retval;
622 }
623
624 // Disconnected calls get primary position if there are no active calls
625 // to let user know quickly what call has disconnected. Disconnected
626 // calls are very short lived.
627 if (!skipDisconnected) {
628 retval = callList.getDisconnectingCall();
629 if (retval != null && retval != ignore) {
630 return retval;
631 }
632 retval = callList.getDisconnectedCall();
633 if (retval != null && retval != ignore) {
634 return retval;
635 }
636 }
637
638 // Then we go to background call (calls on hold)
639 retval = callList.getBackgroundCall();
640 if (retval != null && retval != ignore) {
641 return retval;
642 }
643
644 // Lastly, we go to a second background call.
645 retval = callList.getSecondBackgroundCall();
646
647 return retval;
648 }
649
650 private void updatePrimaryDisplayInfo() {
651 if (mInCallScreen == null) {
652 // TODO: May also occur if search result comes back after ui is destroyed. Look into
653 // removing that case completely.
654 LogUtil.v(
655 "CallCardPresenter.updatePrimaryDisplayInfo",
656 "updatePrimaryDisplayInfo called but ui is null!");
657 return;
658 }
659
660 if (mPrimary == null) {
661 // Clear the primary display info.
662 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
663 return;
664 }
665
666 // Hide the contact photo if we are in a video call and the incoming video surface is
667 // showing.
668 boolean showContactPhoto =
669 !VideoCallPresenter.showIncomingVideo(mPrimary.getVideoState(), mPrimary.getState());
670
671 // DialerCall placed through a work phone account.
672 boolean hasWorkCallProperty = mPrimary.hasProperty(PROPERTY_ENTERPRISE_CALL);
673
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700674 MultimediaData multimediaData = null;
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700675 if (mPrimary.getEnrichedCallSession() != null) {
676 multimediaData = mPrimary.getEnrichedCallSession().getMultimediaData();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700677 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800678
679 if (mPrimary.isConferenceCall()) {
680 LogUtil.v(
681 "CallCardPresenter.updatePrimaryDisplayInfo",
682 "update primary display info for conference call.");
683
684 mInCallScreen.setPrimary(
685 new PrimaryInfo(
686 null /* number */,
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700687 CallerInfoUtils.getConferenceString(
688 mContext, mPrimary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800689 false /* nameIsNumber */,
690 null /* location */,
691 null /* label */,
Eric Erfanian83b20212017-05-31 08:53:10 -0700692 null /* photo */,
Eric Erfanianccca3152017-02-22 16:32:36 -0800693 ContactPhotoType.DEFAULT_PLACEHOLDER,
694 false /* isSipCall */,
695 showContactPhoto,
696 hasWorkCallProperty,
697 false /* isSpam */,
698 false /* answeringDisconnectsOngoingCall */,
699 shouldShowLocation(),
700 null /* contactInfoLookupKey */,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700701 null /* enrichedCallMultimediaData */,
702 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800703 } else if (mPrimaryContactInfo != null) {
704 LogUtil.v(
705 "CallCardPresenter.updatePrimaryDisplayInfo",
706 "update primary display info for " + mPrimaryContactInfo);
707
708 String name = getNameForCall(mPrimaryContactInfo);
709 String number;
710
711 boolean isChildNumberShown = !TextUtils.isEmpty(mPrimary.getChildNumber());
712 boolean isForwardedNumberShown = !TextUtils.isEmpty(mPrimary.getLastForwardedNumber());
713 boolean isCallSubjectShown = shouldShowCallSubject(mPrimary);
714
715 if (isCallSubjectShown) {
716 number = null;
717 } else if (isChildNumberShown) {
718 number = mContext.getString(R.string.child_number, mPrimary.getChildNumber());
719 } else if (isForwardedNumberShown) {
720 // Use last forwarded number instead of second line, if present.
721 number = mPrimary.getLastForwardedNumber();
722 } else {
723 number = mPrimaryContactInfo.number;
724 }
725
726 boolean nameIsNumber = name != null && name.equals(mPrimaryContactInfo.number);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700727
Eric Erfanianccca3152017-02-22 16:32:36 -0800728 // DialerCall with caller that is a work contact.
729 boolean isWorkContact = (mPrimaryContactInfo.userType == ContactsUtils.USER_TYPE_WORK);
730 mInCallScreen.setPrimary(
731 new PrimaryInfo(
732 number,
Eric Erfanianc857f902017-05-15 14:05:33 -0700733 mPrimary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800734 nameIsNumber,
Eric Erfaniand8046e52017-04-06 09:41:50 -0700735 shouldShowLocationAsLabel(nameIsNumber, mPrimaryContactInfo.shouldShowLocation)
736 ? mPrimaryContactInfo.location
737 : null,
Eric Erfanianccca3152017-02-22 16:32:36 -0800738 isChildNumberShown || isCallSubjectShown ? null : mPrimaryContactInfo.label,
739 mPrimaryContactInfo.photo,
740 mPrimaryContactInfo.photoType,
741 mPrimaryContactInfo.isSipCall,
742 showContactPhoto,
743 hasWorkCallProperty || isWorkContact,
744 mPrimary.isSpam(),
745 mPrimary.answeringDisconnectsForegroundVideoCall(),
746 shouldShowLocation(),
747 mPrimaryContactInfo.lookupKey,
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700748 multimediaData,
749 mPrimary.getNumberPresentation()));
Eric Erfanianccca3152017-02-22 16:32:36 -0800750 } else {
751 // Clear the primary display info.
752 mInCallScreen.setPrimary(PrimaryInfo.createEmptyPrimaryInfo());
753 }
754
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700755 if (isInCallScreenReady) {
756 mInCallScreen.showLocationUi(getLocationFragment());
757 } else {
758 LogUtil.i("CallCardPresenter.updatePrimaryDisplayInfo", "UI not ready, not showing location");
759 }
760 }
761
Eric Erfaniand8046e52017-04-06 09:41:50 -0700762 private static boolean shouldShowLocationAsLabel(
763 boolean nameIsNumber, boolean shouldShowLocation) {
764 if (nameIsNumber) {
765 return true;
766 }
767 if (shouldShowLocation) {
768 return true;
769 }
770 return false;
771 }
772
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700773 private Fragment getLocationFragment() {
774 if (!ConfigProviderBindings.get(mContext)
775 .getBoolean(CONFIG_ENABLE_EMERGENCY_LOCATION, CONFIG_ENABLE_EMERGENCY_LOCATION_DEFAULT)) {
776 LogUtil.i("CallCardPresenter.getLocationFragment", "disabled by config.");
777 return null;
778 }
779 if (!shouldShowLocation()) {
780 LogUtil.i("CallCardPresenter.getLocationFragment", "shouldn't show location");
781 return null;
782 }
783 if (!hasLocationPermission()) {
784 LogUtil.i("CallCardPresenter.getLocationFragment", "no location permission.");
785 return null;
786 }
787 if (isBatteryTooLowForEmergencyLocation()) {
788 LogUtil.i("CallCardPresenter.getLocationFragment", "low battery.");
789 return null;
790 }
791 if (ActivityCompat.isInMultiWindowMode(mInCallScreen.getInCallScreenFragment().getActivity())) {
792 LogUtil.i("CallCardPresenter.getLocationFragment", "in multi-window mode");
793 return null;
794 }
795 if (mPrimary.isVideoCall()) {
796 LogUtil.i("CallCardPresenter.getLocationFragment", "emergency video calls not supported");
797 return null;
798 }
799 if (!callLocation.canGetLocation(mContext)) {
800 LogUtil.i("CallCardPresenter.getLocationFragment", "can't get current location");
801 return null;
802 }
803 LogUtil.i("CallCardPresenter.getLocationFragment", "returning location fragment");
804 return callLocation.getLocationFragment(mContext);
Eric Erfanianccca3152017-02-22 16:32:36 -0800805 }
806
807 private boolean shouldShowLocation() {
808 if (isOutgoingEmergencyCall(mPrimary)) {
809 LogUtil.i("CallCardPresenter.shouldShowLocation", "new emergency call");
810 return true;
811 } else if (isIncomingEmergencyCall(mPrimary)) {
812 LogUtil.i("CallCardPresenter.shouldShowLocation", "potential emergency callback");
813 return true;
814 } else if (isIncomingEmergencyCall(mSecondary)) {
815 LogUtil.i("CallCardPresenter.shouldShowLocation", "has potential emergency callback");
816 return true;
817 }
818 return false;
819 }
820
821 private static boolean isOutgoingEmergencyCall(@Nullable DialerCall call) {
822 return call != null && !call.isIncoming() && call.isEmergencyCall();
823 }
824
825 private static boolean isIncomingEmergencyCall(@Nullable DialerCall call) {
826 return call != null && call.isIncoming() && call.isPotentialEmergencyCallback();
827 }
828
829 private boolean hasLocationPermission() {
830 return ContextCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION)
831 == PackageManager.PERMISSION_GRANTED;
832 }
833
834 private boolean isBatteryTooLowForEmergencyLocation() {
835 Intent batteryStatus =
836 mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
837 int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
838 if (status == BatteryManager.BATTERY_STATUS_CHARGING
839 || status == BatteryManager.BATTERY_STATUS_FULL) {
840 // Plugged in or full battery
841 return false;
842 }
843 int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
844 int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
845 float batteryPercent = (100f * level) / scale;
846 long threshold =
847 ConfigProviderBindings.get(mContext)
848 .getLong(
849 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION,
850 CONFIG_MIN_BATTERY_PERCENT_FOR_EMERGENCY_LOCATION_DEFAULT);
851 LogUtil.i(
852 "CallCardPresenter.isBatteryTooLowForEmergencyLocation",
853 "percent charged: " + batteryPercent + ", min required charge: " + threshold);
854 return batteryPercent < threshold;
855 }
856
857 private void updateSecondaryDisplayInfo() {
858 if (mInCallScreen == null) {
859 return;
860 }
861
862 if (mSecondary == null) {
863 // Clear the secondary display info.
864 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
865 return;
866 }
867
Eric Erfanianea7890c2017-06-19 12:40:59 -0700868 if (mSecondary.isMergeInProcess()) {
869 LogUtil.i(
870 "CallCardPresenter.updateSecondaryDisplayInfo",
871 "secondary call is merge in process, clearing info");
872 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
873 return;
874 }
875
Eric Erfanianccca3152017-02-22 16:32:36 -0800876 if (mSecondary.isConferenceCall()) {
877 mInCallScreen.setSecondary(
878 new SecondaryInfo(
879 true /* show */,
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700880 CallerInfoUtils.getConferenceString(
881 mContext, mSecondary.hasProperty(Details.PROPERTY_GENERIC_CONFERENCE)),
Eric Erfanianccca3152017-02-22 16:32:36 -0800882 false /* nameIsNumber */,
883 null /* label */,
884 mSecondary.getCallProviderLabel(),
885 true /* isConference */,
886 mSecondary.isVideoCall(),
887 mIsFullscreen));
888 } else if (mSecondaryContactInfo != null) {
889 LogUtil.v("CallCardPresenter.updateSecondaryDisplayInfo", "" + mSecondaryContactInfo);
890 String name = getNameForCall(mSecondaryContactInfo);
891 boolean nameIsNumber = name != null && name.equals(mSecondaryContactInfo.number);
892 mInCallScreen.setSecondary(
893 new SecondaryInfo(
894 true /* show */,
Eric Erfanianc857f902017-05-15 14:05:33 -0700895 mSecondary.updateNameIfRestricted(name),
Eric Erfanianccca3152017-02-22 16:32:36 -0800896 nameIsNumber,
897 mSecondaryContactInfo.label,
898 mSecondary.getCallProviderLabel(),
899 false /* isConference */,
900 mSecondary.isVideoCall(),
901 mIsFullscreen));
902 } else {
903 // Clear the secondary display info.
904 mInCallScreen.setSecondary(SecondaryInfo.createEmptySecondaryInfo(mIsFullscreen));
905 }
906 }
907
908 /** Returns the gateway number for any existing outgoing call. */
909 private String getGatewayNumber() {
910 if (hasOutgoingGatewayCall()) {
911 return DialerCall.getNumberFromHandle(mPrimary.getGatewayInfo().getGatewayAddress());
912 }
913 return null;
914 }
915
916 /**
917 * Returns the label (line of text above the number/name) for any given call. For example,
918 * "calling via [Account/Google Voice]" for outgoing calls.
919 */
920 private String getConnectionLabel() {
921 if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_PHONE_STATE)
922 != PackageManager.PERMISSION_GRANTED) {
923 return null;
924 }
925 StatusHints statusHints = mPrimary.getStatusHints();
926 if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) {
927 return statusHints.getLabel().toString();
928 }
929
930 if (hasOutgoingGatewayCall() && getUi() != null) {
931 // Return the label for the gateway app on outgoing calls.
932 final PackageManager pm = mContext.getPackageManager();
933 try {
934 ApplicationInfo info =
935 pm.getApplicationInfo(mPrimary.getGatewayInfo().getGatewayProviderPackageName(), 0);
936 return pm.getApplicationLabel(info).toString();
937 } catch (PackageManager.NameNotFoundException e) {
938 LogUtil.e("CallCardPresenter.getConnectionLabel", "gateway Application Not Found.", e);
939 return null;
940 }
941 }
942 return mPrimary.getCallProviderLabel();
943 }
944
945 private Drawable getCallStateIcon() {
946 // Return connection icon if one exists.
947 StatusHints statusHints = mPrimary.getStatusHints();
948 if (statusHints != null && statusHints.getIcon() != null) {
949 Drawable icon = statusHints.getIcon().loadDrawable(mContext);
950 if (icon != null) {
951 return icon;
952 }
953 }
954
955 return null;
956 }
957
958 private boolean hasOutgoingGatewayCall() {
959 // We only display the gateway information while STATE_DIALING so return false for any other
960 // call state.
961 // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which
962 // is also called after a contact search completes (call is not present yet). Split the
963 // UI update so it can receive independent updates.
964 if (mPrimary == null) {
965 return false;
966 }
967 return DialerCall.State.isDialing(mPrimary.getState())
968 && mPrimary.getGatewayInfo() != null
969 && !mPrimary.getGatewayInfo().isEmpty();
970 }
971
972 /** Gets the name to display for the call. */
Eric Erfanian91ce7d22017-06-05 13:35:02 -0700973 private String getNameForCall(ContactCacheEntry contactInfo) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800974 String preferredName =
975 ContactDisplayUtils.getPreferredDisplayName(
976 contactInfo.namePrimary, contactInfo.nameAlternative, mContactsPreferences);
977 if (TextUtils.isEmpty(preferredName)) {
978 return contactInfo.number;
979 }
980 return preferredName;
981 }
982
Eric Erfanianccca3152017-02-22 16:32:36 -0800983 @Override
984 public void onSecondaryInfoClicked() {
985 if (mSecondary == null) {
986 LogUtil.e(
987 "CallCardPresenter.onSecondaryInfoClicked",
988 "secondary info clicked but no secondary call.");
989 return;
990 }
991
992 LogUtil.i(
993 "CallCardPresenter.onSecondaryInfoClicked", "swapping call to foreground: " + mSecondary);
994 mSecondary.unhold();
995 }
996
997 @Override
998 public void onEndCallClicked() {
999 LogUtil.i("CallCardPresenter.onEndCallClicked", "disconnecting call: " + mPrimary);
1000 if (mPrimary != null) {
1001 mPrimary.disconnect();
1002 }
1003 }
1004
1005 /**
1006 * Handles a change to the fullscreen mode of the in-call UI.
1007 *
1008 * @param isFullscreenMode {@code True} if the in-call UI is entering full screen mode.
1009 */
1010 @Override
1011 public void onFullscreenModeChanged(boolean isFullscreenMode) {
1012 mIsFullscreen = isFullscreenMode;
1013 if (mInCallScreen == null) {
1014 return;
1015 }
1016 maybeShowManageConferenceCallButton();
1017 }
1018
1019 private boolean isPrimaryCallActive() {
1020 return mPrimary != null && mPrimary.getState() == DialerCall.State.ACTIVE;
1021 }
1022
Eric Erfanianccca3152017-02-22 16:32:36 -08001023 private boolean shouldShowEndCallButton(DialerCall primary, int callState) {
1024 if (primary == null) {
1025 return false;
1026 }
1027 if ((!DialerCall.State.isConnectingOrConnected(callState)
1028 && callState != DialerCall.State.DISCONNECTING
1029 && callState != DialerCall.State.DISCONNECTED)
1030 || callState == DialerCall.State.INCOMING) {
1031 return false;
1032 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001033 if (mPrimary.getVideoTech().getSessionModificationState()
Eric Erfanian90508232017-03-24 09:31:16 -07001034 == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001035 return false;
1036 }
1037 return true;
1038 }
1039
1040 @Override
1041 public void onInCallScreenResumed() {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001042 updatePrimaryDisplayInfo();
1043
Eric Erfanianccca3152017-02-22 16:32:36 -08001044 if (shouldSendAccessibilityEvent) {
1045 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1046 }
1047 }
1048
Eric Erfaniand8046e52017-04-06 09:41:50 -07001049 @Override
Eric Erfanian91ce7d22017-06-05 13:35:02 -07001050 public void onInCallScreenPaused() {}
Eric Erfaniand8046e52017-04-06 09:41:50 -07001051
Eric Erfanianccca3152017-02-22 16:32:36 -08001052 static boolean sendAccessibilityEvent(Context context, InCallScreen inCallScreen) {
1053 AccessibilityManager am =
1054 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
1055 if (!am.isEnabled()) {
1056 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "accessibility is off");
1057 return false;
1058 }
1059 if (inCallScreen == null) {
1060 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "incallscreen is null");
1061 return false;
1062 }
1063 Fragment fragment = inCallScreen.getInCallScreenFragment();
1064 if (fragment == null || fragment.getView() == null || fragment.getView().getParent() == null) {
1065 LogUtil.w("CallCardPresenter.sendAccessibilityEvent", "fragment/view/parent is null");
1066 return false;
1067 }
1068
1069 DisplayManager displayManager =
1070 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1071 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
1072 boolean screenIsOn = display.getState() == Display.STATE_ON;
1073 LogUtil.d("CallCardPresenter.sendAccessibilityEvent", "screen is on: %b", screenIsOn);
1074 if (!screenIsOn) {
1075 return false;
1076 }
1077
1078 AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT);
1079 inCallScreen.dispatchPopulateAccessibilityEvent(event);
1080 View view = inCallScreen.getInCallScreenFragment().getView();
1081 view.getParent().requestSendAccessibilityEvent(view, event);
1082 return true;
1083 }
1084
1085 private void maybeSendAccessibilityEvent(
1086 InCallState oldState, final InCallState newState, boolean primaryChanged) {
1087 shouldSendAccessibilityEvent = false;
1088 if (mContext == null) {
1089 return;
1090 }
1091 final AccessibilityManager am =
1092 (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
1093 if (!am.isEnabled()) {
1094 return;
1095 }
1096 // Announce the current call if it's new incoming/outgoing call or primary call is changed
1097 // due to switching calls between two ongoing calls (one is on hold).
1098 if ((oldState != InCallState.OUTGOING && newState == InCallState.OUTGOING)
1099 || (oldState != InCallState.INCOMING && newState == InCallState.INCOMING)
1100 || primaryChanged) {
1101 LogUtil.i(
1102 "CallCardPresenter.maybeSendAccessibilityEvent", "schedule accessibility announcement");
1103 shouldSendAccessibilityEvent = true;
1104 handler.postDelayed(sendAccessibilityEventRunnable, ACCESSIBILITY_ANNOUNCEMENT_DELAY_MILLIS);
1105 }
1106 }
1107
1108 /**
1109 * Determines whether the call subject should be visible on the UI. For the call subject to be
1110 * visible, the call has to be in an incoming or waiting state, and the subject must not be empty.
1111 *
1112 * @param call The call.
1113 * @return {@code true} if the subject should be shown, {@code false} otherwise.
1114 */
1115 private boolean shouldShowCallSubject(DialerCall call) {
1116 if (call == null) {
1117 return false;
1118 }
1119
1120 boolean isIncomingOrWaiting =
1121 mPrimary.getState() == DialerCall.State.INCOMING
1122 || mPrimary.getState() == DialerCall.State.CALL_WAITING;
1123 return isIncomingOrWaiting
1124 && !TextUtils.isEmpty(call.getCallSubject())
1125 && call.getNumberPresentation() == TelecomManager.PRESENTATION_ALLOWED
1126 && call.isCallSubjectSupported();
1127 }
1128
1129 /**
1130 * Determines whether the "note sent" toast should be shown. It should be shown for a new outgoing
1131 * call with a subject.
1132 *
1133 * @param call The call
1134 * @return {@code true} if the toast should be shown, {@code false} otherwise.
1135 */
1136 private boolean shouldShowNoteSentToast(DialerCall call) {
1137 return call != null
1138 && hasCallSubject(call)
1139 && (call.getState() == DialerCall.State.DIALING
1140 || call.getState() == DialerCall.State.CONNECTING);
1141 }
1142
1143 private InCallScreen getUi() {
1144 return mInCallScreen;
1145 }
1146
1147 public static class ContactLookupCallback implements ContactInfoCacheCallback {
1148
1149 private final WeakReference<CallCardPresenter> mCallCardPresenter;
1150 private final boolean mIsPrimary;
1151
1152 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) {
1153 mCallCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter);
1154 mIsPrimary = isPrimary;
1155 }
1156
1157 @Override
1158 public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
1159 CallCardPresenter presenter = mCallCardPresenter.get();
1160 if (presenter != null) {
1161 presenter.onContactInfoComplete(callId, entry, mIsPrimary);
1162 }
1163 }
1164
1165 @Override
1166 public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
1167 CallCardPresenter presenter = mCallCardPresenter.get();
1168 if (presenter != null) {
1169 presenter.onImageLoadComplete(callId, entry);
1170 }
1171 }
1172 }
1173}