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 ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary)77 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) { 78 mCallCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter); 79 mIsPrimary = isPrimary; 80 } 81 82 @Override onContactInfoComplete(String callId, ContactCacheEntry entry)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 onImageLoadComplete(String callId, ContactCacheEntry entry)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 CallCardPresenter()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 init(Context context, Call call)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 onUiReady(CallCardUi ui)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 onUiUnready(CallCardUi ui)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 onIncomingCall(InCallState oldState, InCallState newState, Call call)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 onStateChange(InCallState oldState, InCallState newState, CallList callList)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 onDetailsChanged(Call call, android.telecom.Call.Details details)265 public void onDetailsChanged(Call call, android.telecom.Call.Details details) { 266 updatePrimaryCallState(); 267 } 268 getSubscriptionNumber()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 updatePrimaryCallState()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 */ maybeShowManageConferenceCallButton()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 setCallbackNumber()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 updateCallTime()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 onCallStateButtonTouched()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 maybeStartSearch(Call call, boolean isPrimary)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 */ startContactInfoSearch(final Call call, final boolean isPrimary, boolean isIncoming)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 onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary)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 onImageLoadComplete(String callId, ContactCacheEntry entry)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 isConference(Call call)402 private static boolean isConference(Call call) { 403 return call != null && call.isConferenceCall(); 404 } 405 canManageConference(Call call)406 private static boolean canManageConference(Call call) { 407 return call != null && call.can(PhoneCapabilities.MANAGE_CONFERENCE); 408 } 409 updateContactEntry(ContactCacheEntry entry, boolean isPrimary, boolean isConference)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 */ getCallToDisplay(CallList callList, Call ignore, boolean skipDisconnected)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 updatePrimaryDisplayInfo(ContactCacheEntry entry, boolean isConference)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 updateSecondaryDisplayInfo(boolean isConference)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 */ getAccountForCall(Call call)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 */ getGatewayNumber()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 */ getCallProviderIcon(Call call)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 */ getCallProviderLabel(Call call)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 */ getConnectionLabel()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 getConnectionIcon()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 hasOutgoingGatewayCall()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 */ getNameForCall(ContactCacheEntry contactInfo)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 */ getNumberForCall(ContactCacheEntry contactInfo)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 secondaryInfoClicked()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 endCallClicked()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 getNumberFromHandle(Uri handle)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 onFullScreenVideoStateChanged(boolean isFullScreenVideo)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 { setVisible(boolean on)663 void setVisible(boolean on); setCallCardVisible(boolean visible)664 void setCallCardVisible(boolean visible); setPrimary(String number, String name, boolean nameIsNumber, String label, Drawable photo, boolean isConference, boolean canManageConference, boolean isSipCall)665 void setPrimary(String number, String name, boolean nameIsNumber, String label, 666 Drawable photo, boolean isConference, boolean canManageConference, 667 boolean isSipCall); setSecondary(boolean show, String name, boolean nameIsNumber, String label, String providerLabel, Drawable providerIcon, boolean isConference, boolean canManageConference)668 void setSecondary(boolean show, String name, boolean nameIsNumber, String label, 669 String providerLabel, Drawable providerIcon, boolean isConference, 670 boolean canManageConference); setCallState(int state, int videoState, int sessionModificationState, DisconnectCause disconnectCause, String connectionLabel, Drawable connectionIcon, String gatewayNumber)671 void setCallState(int state, int videoState, int sessionModificationState, 672 DisconnectCause disconnectCause, String connectionLabel, 673 Drawable connectionIcon, String gatewayNumber); setPrimaryCallElapsedTime(boolean show, String duration)674 void setPrimaryCallElapsedTime(boolean show, String duration); setPrimaryName(String name, boolean nameIsNumber)675 void setPrimaryName(String name, boolean nameIsNumber); setPrimaryImage(Drawable image)676 void setPrimaryImage(Drawable image); setPrimaryPhoneNumber(String phoneNumber)677 void setPrimaryPhoneNumber(String phoneNumber); setPrimaryLabel(String label)678 void setPrimaryLabel(String label); setEndCallButtonEnabled(boolean enabled, boolean animate)679 void setEndCallButtonEnabled(boolean enabled, boolean animate); setCallbackNumber(String number, boolean isEmergencyCalls)680 void setCallbackNumber(String number, boolean isEmergencyCalls); setPhotoVisible(boolean isVisible)681 void setPhotoVisible(boolean isVisible); setProgressSpinnerVisible(boolean visible)682 void setProgressSpinnerVisible(boolean visible); showManageConferenceCallButton(boolean visible)683 void showManageConferenceCallButton(boolean visible); 684 } 685 getTelecomManager()686 private TelecomManager getTelecomManager() { 687 if (mTelecomManager == null) { 688 mTelecomManager = 689 (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); 690 } 691 return mTelecomManager; 692 } 693 } 694