1 /* 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 17 package com.android.incallui; 18 19 import android.Manifest; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.PackageManager; 24 import android.graphics.drawable.Drawable; 25 import android.net.Uri; 26 import android.os.Bundle; 27 import android.telecom.DisconnectCause; 28 import android.telecom.PhoneAccount; 29 import android.telecom.PhoneAccountHandle; 30 import android.telecom.StatusHints; 31 import android.telecom.TelecomManager; 32 import android.telecom.VideoProfile; 33 import android.telephony.PhoneNumberUtils; 34 import android.telephony.TelephonyManager; 35 import android.text.TextUtils; 36 37 import com.android.incallui.ContactInfoCache.ContactCacheEntry; 38 import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback; 39 import com.android.incallui.InCallPresenter.InCallDetailsListener; 40 import com.android.incallui.InCallPresenter.InCallEventListener; 41 import com.android.incallui.InCallPresenter.InCallState; 42 import com.android.incallui.InCallPresenter.InCallStateListener; 43 import com.android.incallui.InCallPresenter.IncomingCallListener; 44 import com.android.incalluibind.ObjectFactory; 45 46 import java.lang.ref.WeakReference; 47 48 import com.google.common.base.Preconditions; 49 50 /** 51 * Presenter for the Call Card Fragment. 52 * <p> 53 * This class listens for changes to InCallState and passes it along to the fragment. 54 */ 55 public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi> 56 implements InCallStateListener, IncomingCallListener, InCallDetailsListener, 57 InCallEventListener { 58 59 private static final String TAG = CallCardPresenter.class.getSimpleName(); 60 private static final long CALL_TIME_UPDATE_INTERVAL_MS = 1000; 61 62 private Call mPrimary; 63 private Call mSecondary; 64 private ContactCacheEntry mPrimaryContactInfo; 65 private ContactCacheEntry mSecondaryContactInfo; 66 private CallTimer mCallTimer; 67 private Context mContext; 68 69 public static class ContactLookupCallback implements ContactInfoCacheCallback { 70 private final WeakReference<CallCardPresenter> mCallCardPresenter; 71 private final boolean mIsPrimary; 72 ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary)73 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) { 74 mCallCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter); 75 mIsPrimary = isPrimary; 76 } 77 78 @Override onContactInfoComplete(String callId, ContactCacheEntry entry)79 public void onContactInfoComplete(String callId, ContactCacheEntry entry) { 80 CallCardPresenter presenter = mCallCardPresenter.get(); 81 if (presenter != null) { 82 presenter.onContactInfoComplete(callId, entry, mIsPrimary); 83 } 84 } 85 86 @Override onImageLoadComplete(String callId, ContactCacheEntry entry)87 public void onImageLoadComplete(String callId, ContactCacheEntry entry) { 88 CallCardPresenter presenter = mCallCardPresenter.get(); 89 if (presenter != null) { 90 presenter.onImageLoadComplete(callId, entry); 91 } 92 } 93 94 } 95 CallCardPresenter()96 public CallCardPresenter() { 97 // create the call timer 98 mCallTimer = new CallTimer(new Runnable() { 99 @Override 100 public void run() { 101 updateCallTime(); 102 } 103 }); 104 } 105 init(Context context, Call call)106 public void init(Context context, Call call) { 107 mContext = Preconditions.checkNotNull(context); 108 109 // Call may be null if disconnect happened already. 110 if (call != null) { 111 mPrimary = call; 112 113 // start processing lookups right away. 114 if (!call.isConferenceCall()) { 115 startContactInfoSearch(call, true, call.getState() == Call.State.INCOMING); 116 } else { 117 updateContactEntry(null, true); 118 } 119 } 120 } 121 122 @Override onUiReady(CallCardUi ui)123 public void onUiReady(CallCardUi ui) { 124 super.onUiReady(ui); 125 126 // Contact search may have completed before ui is ready. 127 if (mPrimaryContactInfo != null) { 128 updatePrimaryDisplayInfo(); 129 } 130 131 // Register for call state changes last 132 InCallPresenter.getInstance().addListener(this); 133 InCallPresenter.getInstance().addIncomingCallListener(this); 134 InCallPresenter.getInstance().addDetailsListener(this); 135 InCallPresenter.getInstance().addInCallEventListener(this); 136 } 137 138 @Override onUiUnready(CallCardUi ui)139 public void onUiUnready(CallCardUi ui) { 140 super.onUiUnready(ui); 141 142 // stop getting call state changes 143 InCallPresenter.getInstance().removeListener(this); 144 InCallPresenter.getInstance().removeIncomingCallListener(this); 145 InCallPresenter.getInstance().removeDetailsListener(this); 146 InCallPresenter.getInstance().removeInCallEventListener(this); 147 148 mPrimary = null; 149 mPrimaryContactInfo = null; 150 mSecondaryContactInfo = null; 151 } 152 153 @Override onIncomingCall(InCallState oldState, InCallState newState, Call call)154 public void onIncomingCall(InCallState oldState, InCallState newState, Call call) { 155 // same logic should happen as with onStateChange() 156 onStateChange(oldState, newState, CallList.getInstance()); 157 } 158 159 @Override onStateChange(InCallState oldState, InCallState newState, CallList callList)160 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) { 161 Log.d(this, "onStateChange() " + newState); 162 final CallCardUi ui = getUi(); 163 if (ui == null) { 164 return; 165 } 166 167 Call primary = null; 168 Call secondary = null; 169 170 if (newState == InCallState.INCOMING) { 171 primary = callList.getIncomingCall(); 172 } else if (newState == InCallState.PENDING_OUTGOING || newState == InCallState.OUTGOING) { 173 primary = callList.getOutgoingCall(); 174 if (primary == null) { 175 primary = callList.getPendingOutgoingCall(); 176 } 177 178 // getCallToDisplay doesn't go through outgoing or incoming calls. It will return the 179 // highest priority call to display as the secondary call. 180 secondary = getCallToDisplay(callList, null, true); 181 } else if (newState == InCallState.INCALL) { 182 primary = getCallToDisplay(callList, null, false); 183 secondary = getCallToDisplay(callList, primary, true); 184 } 185 186 Log.d(this, "Primary call: " + primary); 187 Log.d(this, "Secondary call: " + secondary); 188 189 final boolean primaryChanged = !Call.areSame(mPrimary, primary); 190 final boolean secondaryChanged = !Call.areSame(mSecondary, secondary); 191 192 mSecondary = secondary; 193 mPrimary = primary; 194 195 // Refresh primary call information if either: 196 // 1. Primary call changed. 197 // 2. The call's ability to manage conference has changed. 198 if (mPrimary != null && (primaryChanged || 199 ui.isManageConferenceVisible() != shouldShowManageConference())) { 200 // primary call has changed 201 mPrimaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(mContext, mPrimary, 202 mPrimary.getState() == Call.State.INCOMING); 203 updatePrimaryDisplayInfo(); 204 maybeStartSearch(mPrimary, true); 205 mPrimary.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); 206 } 207 208 if (mSecondary == null) { 209 // Secondary call may have ended. Update the ui. 210 mSecondaryContactInfo = null; 211 updateSecondaryDisplayInfo(); 212 } else if (secondaryChanged) { 213 // secondary call has changed 214 mSecondaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(mContext, mSecondary, 215 mSecondary.getState() == Call.State.INCOMING); 216 updateSecondaryDisplayInfo(); 217 maybeStartSearch(mSecondary, false); 218 mSecondary.setSessionModificationState(Call.SessionModificationState.NO_REQUEST); 219 } 220 221 // Start/stop timers. 222 if (mPrimary != null && mPrimary.getState() == Call.State.ACTIVE) { 223 Log.d(this, "Starting the calltime timer"); 224 mCallTimer.start(CALL_TIME_UPDATE_INTERVAL_MS); 225 } else { 226 Log.d(this, "Canceling the calltime timer"); 227 mCallTimer.cancel(); 228 ui.setPrimaryCallElapsedTime(false, 0); 229 } 230 231 // Set the call state 232 int callState = Call.State.IDLE; 233 if (mPrimary != null) { 234 callState = mPrimary.getState(); 235 updatePrimaryCallState(); 236 } else { 237 getUi().setCallState( 238 callState, 239 VideoProfile.VideoState.AUDIO_ONLY, 240 Call.SessionModificationState.NO_REQUEST, 241 new DisconnectCause(DisconnectCause.UNKNOWN), 242 null, 243 null, 244 null); 245 } 246 247 // Hide/show the contact photo based on the video state. 248 // If the primary call is a video call on hold, still show the contact photo. 249 // If the primary call is an active video call, hide the contact photo. 250 if (mPrimary != null) { 251 getUi().setPhotoVisible(!(mPrimary.isVideoCall(mContext) && 252 callState != Call.State.ONHOLD)); 253 } 254 255 maybeShowManageConferenceCallButton(); 256 257 final boolean enableEndCallButton = (Call.State.isConnectingOrConnected(callState) 258 || callState == Call.State.DISCONNECTING) && 259 callState != Call.State.INCOMING && mPrimary != null; 260 // Hide the end call button instantly if we're receiving an incoming call. 261 getUi().setEndCallButtonEnabled( 262 enableEndCallButton, callState != Call.State.INCOMING /* animate */); 263 } 264 265 @Override onDetailsChanged(Call call, android.telecom.Call.Details details)266 public void onDetailsChanged(Call call, android.telecom.Call.Details details) { 267 updatePrimaryCallState(); 268 269 if (call.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE) != 270 android.telecom.Call.Details.can( 271 details.getCallCapabilities(), 272 android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)) { 273 maybeShowManageConferenceCallButton(); 274 } 275 } 276 getSubscriptionNumber()277 private String getSubscriptionNumber() { 278 // If it's an emergency call, and they're not populating the callback number, 279 // then try to fall back to the phone sub info (to hopefully get the SIM's 280 // number directly from the telephony layer). 281 PhoneAccountHandle accountHandle = mPrimary.getAccountHandle(); 282 if (accountHandle != null) { 283 TelecomManager mgr = InCallPresenter.getInstance().getTelecomManager(); 284 PhoneAccount account = mgr.getPhoneAccount(accountHandle); 285 if (account != null) { 286 return getNumberFromHandle(account.getSubscriptionAddress()); 287 } 288 } 289 return null; 290 } 291 updatePrimaryCallState()292 private void updatePrimaryCallState() { 293 if (getUi() != null && mPrimary != null) { 294 getUi().setCallState( 295 mPrimary.getState(), 296 mPrimary.getVideoState(), 297 mPrimary.getSessionModificationState(), 298 mPrimary.getDisconnectCause(), 299 getConnectionLabel(), 300 getCallStateIcon(), 301 getGatewayNumber()); 302 setCallbackNumber(); 303 } 304 } 305 306 /** 307 * Only show the conference call button if we can manage the conference. 308 */ maybeShowManageConferenceCallButton()309 private void maybeShowManageConferenceCallButton() { 310 getUi().showManageConferenceCallButton(shouldShowManageConference()); 311 } 312 313 /** 314 * Determines if the manage conference button should be visible, based on the current primary 315 * call. 316 * 317 * @return {@code True} if the manage conference button should be visible. 318 */ shouldShowManageConference()319 private boolean shouldShowManageConference() { 320 if (mPrimary == null) { 321 return false; 322 } 323 324 return mPrimary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE); 325 } 326 setCallbackNumber()327 private void setCallbackNumber() { 328 String callbackNumber = null; 329 330 boolean isEmergencyCall = PhoneNumberUtils.isEmergencyNumber( 331 getNumberFromHandle(mPrimary.getHandle())); 332 if (isEmergencyCall) { 333 callbackNumber = getSubscriptionNumber(); 334 } else { 335 StatusHints statusHints = mPrimary.getTelecommCall().getDetails().getStatusHints(); 336 if (statusHints != null) { 337 Bundle extras = statusHints.getExtras(); 338 if (extras != null) { 339 callbackNumber = extras.getString(TelecomManager.EXTRA_CALL_BACK_NUMBER); 340 } 341 } 342 } 343 344 TelecomManager mgr = InCallPresenter.getInstance().getTelecomManager(); 345 String simNumber = mgr.getLine1Number(mPrimary.getAccountHandle()); 346 if (PhoneNumberUtils.compare(callbackNumber, simNumber)) { 347 Log.d(this, "Numbers are the same; not showing the callback number"); 348 callbackNumber = null; 349 } 350 351 getUi().setCallbackNumber(callbackNumber, isEmergencyCall); 352 } 353 updateCallTime()354 public void updateCallTime() { 355 final CallCardUi ui = getUi(); 356 357 if (ui == null || mPrimary == null || mPrimary.getState() != Call.State.ACTIVE) { 358 if (ui != null) { 359 ui.setPrimaryCallElapsedTime(false, 0); 360 } 361 mCallTimer.cancel(); 362 } else { 363 final long callStart = mPrimary.getConnectTimeMillis(); 364 final long duration = System.currentTimeMillis() - callStart; 365 ui.setPrimaryCallElapsedTime(true, duration); 366 } 367 } 368 onCallStateButtonTouched()369 public void onCallStateButtonTouched() { 370 Intent broadcastIntent = ObjectFactory.getCallStateButtonBroadcastIntent(mContext); 371 if (broadcastIntent != null) { 372 Log.d(this, "Sending call state button broadcast: ", broadcastIntent); 373 mContext.sendBroadcast(broadcastIntent, Manifest.permission.READ_PHONE_STATE); 374 } 375 } 376 maybeStartSearch(Call call, boolean isPrimary)377 private void maybeStartSearch(Call call, boolean isPrimary) { 378 // no need to start search for conference calls which show generic info. 379 if (call != null && !call.isConferenceCall()) { 380 startContactInfoSearch(call, isPrimary, call.getState() == Call.State.INCOMING); 381 } 382 } 383 384 /** 385 * Starts a query for more contact data for the save primary and secondary calls. 386 */ startContactInfoSearch(final Call call, final boolean isPrimary, boolean isIncoming)387 private void startContactInfoSearch(final Call call, final boolean isPrimary, 388 boolean isIncoming) { 389 final ContactInfoCache cache = ContactInfoCache.getInstance(mContext); 390 391 cache.findInfo(call, isIncoming, new ContactLookupCallback(this, isPrimary)); 392 } 393 onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary)394 private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) { 395 updateContactEntry(entry, isPrimary); 396 if (entry.name != null) { 397 Log.d(TAG, "Contact found: " + entry); 398 } 399 if (entry.contactUri != null) { 400 CallerInfoUtils.sendViewNotification(mContext, entry.contactUri); 401 } 402 } 403 onImageLoadComplete(String callId, ContactCacheEntry entry)404 private void onImageLoadComplete(String callId, ContactCacheEntry entry) { 405 if (getUi() == null) { 406 return; 407 } 408 409 if (entry.photo != null) { 410 if (mPrimary != null && callId.equals(mPrimary.getId())) { 411 getUi().setPrimaryImage(entry.photo); 412 } 413 } 414 } 415 updateContactEntry(ContactCacheEntry entry, boolean isPrimary)416 private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) { 417 if (isPrimary) { 418 mPrimaryContactInfo = entry; 419 updatePrimaryDisplayInfo(); 420 } else { 421 mSecondaryContactInfo = entry; 422 updateSecondaryDisplayInfo(); 423 } 424 } 425 426 /** 427 * Get the highest priority call to display. 428 * Goes through the calls and chooses which to return based on priority of which type of call 429 * to display to the user. Callers can use the "ignore" feature to get the second best call 430 * by passing a previously found primary call as ignore. 431 * 432 * @param ignore A call to ignore if found. 433 */ getCallToDisplay(CallList callList, Call ignore, boolean skipDisconnected)434 private Call getCallToDisplay(CallList callList, Call ignore, boolean skipDisconnected) { 435 436 // Active calls come second. An active call always gets precedent. 437 Call retval = callList.getActiveCall(); 438 if (retval != null && retval != ignore) { 439 return retval; 440 } 441 442 // Disconnected calls get primary position if there are no active calls 443 // to let user know quickly what call has disconnected. Disconnected 444 // calls are very short lived. 445 if (!skipDisconnected) { 446 retval = callList.getDisconnectingCall(); 447 if (retval != null && retval != ignore) { 448 return retval; 449 } 450 retval = callList.getDisconnectedCall(); 451 if (retval != null && retval != ignore) { 452 return retval; 453 } 454 } 455 456 // Then we go to background call (calls on hold) 457 retval = callList.getBackgroundCall(); 458 if (retval != null && retval != ignore) { 459 return retval; 460 } 461 462 // Lastly, we go to a second background call. 463 retval = callList.getSecondBackgroundCall(); 464 465 return retval; 466 } 467 updatePrimaryDisplayInfo()468 private void updatePrimaryDisplayInfo() { 469 final CallCardUi ui = getUi(); 470 if (ui == null) { 471 // TODO: May also occur if search result comes back after ui is destroyed. Look into 472 // removing that case completely. 473 Log.d(TAG, "updatePrimaryDisplayInfo called but ui is null!"); 474 return; 475 } 476 477 if (mPrimary == null) { 478 // Clear the primary display info. 479 ui.setPrimary(null, null, false, null, null, false); 480 return; 481 } 482 483 if (mPrimary.isConferenceCall()) { 484 Log.d(TAG, "Update primary display info for conference call."); 485 486 ui.setPrimary( 487 null /* number */, 488 getConferenceString(mPrimary), 489 false /* nameIsNumber */, 490 null /* label */, 491 getConferencePhoto(mPrimary), 492 false /* isSipCall */); 493 } else if (mPrimaryContactInfo != null) { 494 Log.d(TAG, "Update primary display info for " + mPrimaryContactInfo); 495 496 String name = getNameForCall(mPrimaryContactInfo); 497 String number = getNumberForCall(mPrimaryContactInfo); 498 boolean nameIsNumber = name != null && name.equals(mPrimaryContactInfo.number); 499 ui.setPrimary( 500 number, 501 name, 502 nameIsNumber, 503 mPrimaryContactInfo.label, 504 mPrimaryContactInfo.photo, 505 mPrimaryContactInfo.isSipCall); 506 } else { 507 // Clear the primary display info. 508 ui.setPrimary(null, null, false, null, null, false); 509 } 510 511 } 512 updateSecondaryDisplayInfo()513 private void updateSecondaryDisplayInfo() { 514 final CallCardUi ui = getUi(); 515 if (ui == null) { 516 return; 517 } 518 519 if (mSecondary == null) { 520 // Clear the secondary display info. 521 ui.setSecondary(false, null, false, null, null, false /* isConference */); 522 return; 523 } 524 525 if (mSecondary.isConferenceCall()) { 526 ui.setSecondary( 527 true /* show */, 528 getConferenceString(mSecondary), 529 false /* nameIsNumber */, 530 null /* label */, 531 getCallProviderLabel(mSecondary), 532 true /* isConference */); 533 } else if (mSecondaryContactInfo != null) { 534 Log.d(TAG, "updateSecondaryDisplayInfo() " + mSecondaryContactInfo); 535 String name = getNameForCall(mSecondaryContactInfo); 536 boolean nameIsNumber = name != null && name.equals(mSecondaryContactInfo.number); 537 ui.setSecondary( 538 true /* show */, 539 name, 540 nameIsNumber, 541 mSecondaryContactInfo.label, 542 getCallProviderLabel(mSecondary), 543 false /* isConference */); 544 } else { 545 // Clear the secondary display info. 546 ui.setSecondary(false, null, false, null, null, false /* isConference */); 547 } 548 } 549 550 551 /** 552 * Gets the phone account to display for a call. 553 */ getAccountForCall(Call call)554 private PhoneAccount getAccountForCall(Call call) { 555 PhoneAccountHandle accountHandle = call.getAccountHandle(); 556 if (accountHandle == null) { 557 return null; 558 } 559 return InCallPresenter.getInstance().getTelecomManager().getPhoneAccount(accountHandle); 560 } 561 562 /** 563 * Returns the gateway number for any existing outgoing call. 564 */ getGatewayNumber()565 private String getGatewayNumber() { 566 if (hasOutgoingGatewayCall()) { 567 return getNumberFromHandle(mPrimary.getGatewayInfo().getGatewayAddress()); 568 } 569 return null; 570 } 571 572 /** 573 * Return the string label to represent the call provider 574 */ getCallProviderLabel(Call call)575 private String getCallProviderLabel(Call call) { 576 PhoneAccount account = getAccountForCall(call); 577 TelecomManager mgr = InCallPresenter.getInstance().getTelecomManager(); 578 if (account != null && !TextUtils.isEmpty(account.getLabel()) 579 && mgr.hasMultipleCallCapableAccounts()) { 580 return account.getLabel().toString(); 581 } 582 return null; 583 } 584 585 /** 586 * Returns the label (line of text above the number/name) for any given call. 587 * For example, "calling via [Account/Google Voice]" for outgoing calls. 588 */ getConnectionLabel()589 private String getConnectionLabel() { 590 StatusHints statusHints = mPrimary.getTelecommCall().getDetails().getStatusHints(); 591 if (statusHints != null && !TextUtils.isEmpty(statusHints.getLabel())) { 592 return statusHints.getLabel().toString(); 593 } 594 595 if (hasOutgoingGatewayCall() && getUi() != null) { 596 // Return the label for the gateway app on outgoing calls. 597 final PackageManager pm = mContext.getPackageManager(); 598 try { 599 ApplicationInfo info = pm.getApplicationInfo( 600 mPrimary.getGatewayInfo().getGatewayProviderPackageName(), 0); 601 return pm.getApplicationLabel(info).toString(); 602 } catch (PackageManager.NameNotFoundException e) { 603 Log.e(this, "Gateway Application Not Found.", e); 604 return null; 605 } 606 } 607 return getCallProviderLabel(mPrimary); 608 } 609 getCallStateIcon()610 private Drawable getCallStateIcon() { 611 // Return connection icon if one exists. 612 StatusHints statusHints = mPrimary.getTelecommCall().getDetails().getStatusHints(); 613 if (statusHints != null && statusHints.getIconResId() != 0) { 614 Drawable icon = statusHints.getIcon(mContext); 615 if (icon != null) { 616 return icon; 617 } 618 } 619 620 // Return high definition audio icon if the capability is indicated. 621 if (mPrimary.getTelecommCall().getDetails().can( 622 android.telecom.Call.Details.CAPABILITY_HIGH_DEF_AUDIO) 623 && mPrimary.getState() == Call.State.ACTIVE) { 624 return mContext.getResources().getDrawable(R.drawable.ic_hd_audio); 625 } 626 627 return null; 628 } 629 hasOutgoingGatewayCall()630 private boolean hasOutgoingGatewayCall() { 631 // We only display the gateway information while STATE_DIALING so return false for any othe 632 // call state. 633 // TODO: mPrimary can be null because this is called from updatePrimaryDisplayInfo which 634 // is also called after a contact search completes (call is not present yet). Split the 635 // UI update so it can receive independent updates. 636 if (mPrimary == null) { 637 return false; 638 } 639 return Call.State.isDialing(mPrimary.getState()) && mPrimary.getGatewayInfo() != null && 640 !mPrimary.getGatewayInfo().isEmpty(); 641 } 642 643 /** 644 * Gets the name to display for the call. 645 */ getNameForCall(ContactCacheEntry contactInfo)646 private static String getNameForCall(ContactCacheEntry contactInfo) { 647 if (TextUtils.isEmpty(contactInfo.name)) { 648 return contactInfo.number; 649 } 650 return contactInfo.name; 651 } 652 653 /** 654 * Gets the number to display for a call. 655 */ getNumberForCall(ContactCacheEntry contactInfo)656 private static String getNumberForCall(ContactCacheEntry contactInfo) { 657 // If the name is empty, we use the number for the name...so dont show a second 658 // number in the number field 659 if (TextUtils.isEmpty(contactInfo.name)) { 660 return contactInfo.location; 661 } 662 return contactInfo.number; 663 } 664 secondaryInfoClicked()665 public void secondaryInfoClicked() { 666 if (mSecondary == null) { 667 Log.w(this, "Secondary info clicked but no secondary call."); 668 return; 669 } 670 671 Log.i(this, "Swapping call to foreground: " + mSecondary); 672 TelecomAdapter.getInstance().unholdCall(mSecondary.getId()); 673 } 674 endCallClicked()675 public void endCallClicked() { 676 if (mPrimary == null) { 677 return; 678 } 679 680 Log.i(this, "Disconnecting call: " + mPrimary); 681 mPrimary.setState(Call.State.DISCONNECTING); 682 CallList.getInstance().onUpdate(mPrimary); 683 TelecomAdapter.getInstance().disconnectCall(mPrimary.getId()); 684 } 685 getNumberFromHandle(Uri handle)686 private String getNumberFromHandle(Uri handle) { 687 return handle == null ? "" : handle.getSchemeSpecificPart(); 688 } 689 690 /** 691 * Handles a change to the full screen video state. 692 * 693 * @param isFullScreenVideo {@code True} if the application is entering full screen video mode. 694 */ 695 @Override onFullScreenVideoStateChanged(boolean isFullScreenVideo)696 public void onFullScreenVideoStateChanged(boolean isFullScreenVideo) { 697 final CallCardUi ui = getUi(); 698 if (ui == null) { 699 return; 700 } 701 ui.setCallCardVisible(!isFullScreenVideo); 702 } 703 getConferenceString(Call call)704 private String getConferenceString(Call call) { 705 boolean isGenericConference = call.can( 706 android.telecom.Call.Details.CAPABILITY_GENERIC_CONFERENCE); 707 Log.v(this, "getConferenceString: " + isGenericConference); 708 709 final int resId = isGenericConference 710 ? R.string.card_title_in_call : R.string.card_title_conf_call; 711 return mContext.getResources().getString(resId); 712 } 713 getConferencePhoto(Call call)714 private Drawable getConferencePhoto(Call call) { 715 boolean isGenericConference = call.can( 716 android.telecom.Call.Details.CAPABILITY_GENERIC_CONFERENCE); 717 Log.v(this, "getConferencePhoto: " + isGenericConference); 718 719 final int resId = isGenericConference 720 ? R.drawable.img_phone : R.drawable.img_conference; 721 Drawable photo = mContext.getResources().getDrawable(resId); 722 photo.setAutoMirrored(true); 723 return photo; 724 } 725 726 public interface CallCardUi extends Ui { setVisible(boolean on)727 void setVisible(boolean on); setCallCardVisible(boolean visible)728 void setCallCardVisible(boolean visible); setPrimary(String number, String name, boolean nameIsNumber, String label, Drawable photo, boolean isSipCall)729 void setPrimary(String number, String name, boolean nameIsNumber, String label, 730 Drawable photo, boolean isSipCall); setSecondary(boolean show, String name, boolean nameIsNumber, String label, String providerLabel, boolean isConference)731 void setSecondary(boolean show, String name, boolean nameIsNumber, String label, 732 String providerLabel, boolean isConference); setCallState(int state, int videoState, int sessionModificationState, DisconnectCause disconnectCause, String connectionLabel, Drawable connectionIcon, String gatewayNumber)733 void setCallState(int state, int videoState, int sessionModificationState, 734 DisconnectCause disconnectCause, String connectionLabel, 735 Drawable connectionIcon, String gatewayNumber); setPrimaryCallElapsedTime(boolean show, long duration)736 void setPrimaryCallElapsedTime(boolean show, long duration); setPrimaryName(String name, boolean nameIsNumber)737 void setPrimaryName(String name, boolean nameIsNumber); setPrimaryImage(Drawable image)738 void setPrimaryImage(Drawable image); setPrimaryPhoneNumber(String phoneNumber)739 void setPrimaryPhoneNumber(String phoneNumber); setPrimaryLabel(String label)740 void setPrimaryLabel(String label); setEndCallButtonEnabled(boolean enabled, boolean animate)741 void setEndCallButtonEnabled(boolean enabled, boolean animate); setCallbackNumber(String number, boolean isEmergencyCalls)742 void setCallbackNumber(String number, boolean isEmergencyCalls); setPhotoVisible(boolean isVisible)743 void setPhotoVisible(boolean isVisible); setProgressSpinnerVisible(boolean visible)744 void setProgressSpinnerVisible(boolean visible); showManageConferenceCallButton(boolean visible)745 void showManageConferenceCallButton(boolean visible); isManageConferenceVisible()746 boolean isManageConferenceVisible(); 747 } 748 } 749