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