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 17package com.android.incallui; 18 19import android.Manifest; 20import android.content.Context; 21import android.content.Intent; 22import android.content.pm.ApplicationInfo; 23import android.content.pm.PackageManager; 24import android.graphics.drawable.Drawable; 25import android.net.Uri; 26import android.os.Bundle; 27import android.telecom.DisconnectCause; 28import android.telecom.PhoneAccount; 29import android.telecom.PhoneAccountHandle; 30import android.telecom.StatusHints; 31import android.telecom.TelecomManager; 32import android.telecom.VideoProfile; 33import android.telephony.PhoneNumberUtils; 34import android.telephony.TelephonyManager; 35import android.text.TextUtils; 36 37import com.android.incallui.ContactInfoCache.ContactCacheEntry; 38import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback; 39import com.android.incallui.InCallPresenter.InCallDetailsListener; 40import com.android.incallui.InCallPresenter.InCallEventListener; 41import com.android.incallui.InCallPresenter.InCallState; 42import com.android.incallui.InCallPresenter.InCallStateListener; 43import com.android.incallui.InCallPresenter.IncomingCallListener; 44import com.android.incalluibind.ObjectFactory; 45 46import java.lang.ref.WeakReference; 47 48import 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 */ 55public 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 73 public ContactLookupCallback(CallCardPresenter callCardPresenter, boolean isPrimary) { 74 mCallCardPresenter = new WeakReference<CallCardPresenter>(callCardPresenter); 75 mIsPrimary = isPrimary; 76 } 77 78 @Override 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 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 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 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 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 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 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 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 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 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 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 */ 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 */ 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 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 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 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 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 */ 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 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 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 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 */ 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 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 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 */ 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 */ 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 */ 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 */ 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 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 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 */ 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 */ 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 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 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 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 696 public void onFullScreenVideoStateChanged(boolean isFullScreenVideo) { 697 final CallCardUi ui = getUi(); 698 if (ui == null) { 699 return; 700 } 701 ui.setCallCardVisible(!isFullScreenVideo); 702 } 703 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 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 { 727 void setVisible(boolean on); 728 void setCallCardVisible(boolean visible); 729 void setPrimary(String number, String name, boolean nameIsNumber, String label, 730 Drawable photo, boolean isSipCall); 731 void setSecondary(boolean show, String name, boolean nameIsNumber, String label, 732 String providerLabel, boolean isConference); 733 void setCallState(int state, int videoState, int sessionModificationState, 734 DisconnectCause disconnectCause, String connectionLabel, 735 Drawable connectionIcon, String gatewayNumber); 736 void setPrimaryCallElapsedTime(boolean show, long duration); 737 void setPrimaryName(String name, boolean nameIsNumber); 738 void setPrimaryImage(Drawable image); 739 void setPrimaryPhoneNumber(String phoneNumber); 740 void setPrimaryLabel(String label); 741 void setEndCallButtonEnabled(boolean enabled, boolean animate); 742 void setCallbackNumber(String number, boolean isEmergencyCalls); 743 void setPhotoVisible(boolean isVisible); 744 void setProgressSpinnerVisible(boolean visible); 745 void showManageConferenceCallButton(boolean visible); 746 boolean isManageConferenceVisible(); 747 } 748} 749