summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTyler Gunn <tgunn@google.com>2014-11-04 14:59:14 -0800
committerSteve Kondik <steve@cyngn.com>2014-12-28 00:47:02 -0800
commit69dd0f6e102b777b86f2c1213ac5629c15352ddd (patch)
treef5d32e47f9d3e66a5704565bb4a11e3b13db65e3
parent013959488e93d3657bc4ffd3f59e39ba71ea3130 (diff)
downloadpackages_apps_InCallUI-69dd0f6e102b777b86f2c1213ac5629c15352ddd.tar.gz
packages_apps_InCallUI-69dd0f6e102b777b86f2c1213ac5629c15352ddd.tar.bz2
packages_apps_InCallUI-69dd0f6e102b777b86f2c1213ac5629c15352ddd.zip
Fixing conference participant bug.
Fixing a bug where the conference participant calls, having never been an active or background call, will never have a cached contact info record created, causing them to not show up in the conference manager. Bug: 18228141 Change-Id: I15999c983b5d6ce9600b71ad8d8e28582a328c95 Update UI based on MANAGE_CONFERENCE capability. - Remove the manage conference button if capability disabled. - Close the conference call manager if the capability is removed while the conference manager is visible. Bug: 17429707 Change-Id: Id242d776aa7b677edd4604b7c23caee315154b1b Update Conference Manager UI after capability changes. Before, we just set the visibility of the disconnect and separate buttons based on the initial call capabilities when the fragment is created. This can actually change, for example if an IMS call changes to a circuit switch GMS call. To address this, now implement a call details listener on the conference manager presenter, and update the UI if there is a change to disconnect or separate capabilities on a call. This required rearranging some code in the fragment to accomodate these changes; we now no longer assume the code to set the visibility of the buttons is only called once. Bug: 17429707 Change-Id: I476202a21a8ce07568170cd0e046697df84df991 Add Null check for CanAddCall. Missing null check in recently added CanAddCall property. Bug: 18358924 Change-Id: Ia94e621b339fbd08ad074baa6eec787a90bb0fa4 Conference event package performance improvement. Fixing bug where conference participants flash up in the incall UI before the conference is established. The new connections are added as NEW->IDLE. Bug: 18057361 Change-Id: Idf317b25468515fec757afa2aa41945e8222a8f4 Make add-call a global property of telecom. (4/4) ADD_CALL didn't make sense as a property of Connection or Call. This changes it to be a global property instead. Bug: 18285352 Change-Id: If43b30c54a6e6438933b19b8e3e628c9c02d3960 Add support for >5 participants to InCall manage conference UI. - Previous UI had space in the layout for 5 participants only. - Replaced the 5 static participant slots with a ListView. - Created a new Adapter to populate the list. - Added logic in the adapter to request contact info and photo from the contact info cache -- this is required for conference event package participants as they may not have had that information loaded yet. Bug: 18201339 Change-Id: Ieb8038922d2cb4cb1dfce392cf5889e966ff2895 Don't let touches in ConferenceManagerFragment fall through Prevent touches from triggering talkback on elements underneath the ConferenceManagerFragment Bug: 18269622 Change-Id: I98e28942bcb5c51fdc6a147a2d3b797ca1f100ae Add Null check for CanAddCall. Missing null check in recently added CanAddCall property. Bug: 18358924 Change-Id: Ia94e621b339fbd08ad074baa6eec787a90bb0fa4 Use conference call label for IMS calls. - Use GENERIC_CONFERENCE capability to show CDMA-scenario string and image asset. - Since mPrimary/mSecondary and others are class variables, don't pass them around all the time through functions if we don't need to. - Split out the isConference logic for setting the call cards into its own block. - Move logic for specifying conference string / icon into the CallCardPresenter. This makes more sense here, and means that we don't need to pass around a bunch of call paraemters to the fragment. Not all these changes are strictly necessary for the change I need to do, but I had made them originally because I had been thinking of using the VoLTE capability. Even though now I use GENERIC capability, I think the changes are still an improvement in terms of organization though. Bug: 18284408 Change-Id: I41d7825611456e9ea524db9dd2ef19c9646ab7e2 Change-Id: I41d7825611456e9ea524db9dd2ef19c9646ab7e2
-rw-r--r--res/layout/conference_manager_fragment.xml53
-rw-r--r--src/com/android/incallui/Call.java36
-rw-r--r--src/com/android/incallui/CallButtonPresenter.java29
-rw-r--r--src/com/android/incallui/CallCardFragment.java44
-rw-r--r--src/com/android/incallui/CallCardPresenter.java190
-rw-r--r--src/com/android/incallui/CallList.java1
-rw-r--r--src/com/android/incallui/ConferenceManagerFragment.java110
-rw-r--r--src/com/android/incallui/ConferenceManagerPresenter.java132
-rw-r--r--src/com/android/incallui/ConferenceParticipantListAdapter.java503
-rw-r--r--src/com/android/incallui/InCallPresenter.java23
-rw-r--r--src/com/android/incallui/TelecomAdapter.java5
11 files changed, 776 insertions, 350 deletions
diff --git a/res/layout/conference_manager_fragment.xml b/res/layout/conference_manager_fragment.xml
index c6c1af9d..5aed520b 100644
--- a/res/layout/conference_manager_fragment.xml
+++ b/res/layout/conference_manager_fragment.xml
@@ -23,52 +23,15 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="@dimen/conference_call_manager_padding_top"
+ android:clickable="true"
android:visibility="gone">
- <!-- The scrollview wrapper for the list of callers on
- the conference call (in case the list gets too long). -->
- <ScrollView
- android:id="@+id/conferenceList"
+ <!-- List of conference participants. -->
+ <ListView
+ android:id="@+id/participantList"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <!-- The actual list of callers; this embedded LinearLayout
- required since scrollview only supports a single child. -->
- <LinearLayout
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <!-- A conference can have at most MAX_CALLERS_IN_CONFERENCE (= 5) callers,
- so just define all those UI elements here. -->
-
- <!-- Caller 0 -->
- <include
- layout="@layout/caller_in_conference"
- android:id="@+id/caller0"/>
-
- <!-- Caller 1 -->
- <include
- layout="@layout/caller_in_conference"
- android:id="@+id/caller1"/>
-
- <!-- Caller 2 -->
- <include
- layout="@layout/caller_in_conference"
- android:id="@+id/caller2"/>
-
- <!-- Caller 3 -->
- <include
- layout="@layout/caller_in_conference"
- android:id="@+id/caller3"/>
-
- <!-- Caller 4 -->
- <include
- layout="@layout/caller_in_conference"
- android:id="@+id/caller4"/>
-
- </LinearLayout> <!-- End of "list of callers on conference call" -->
-
- </ScrollView> <!-- End of scrolling list wrapper for the linear layout -->
-
+ android:layout_height="match_parent"
+ android:listSelector="@null"
+ android:background="@color/background_dialer_white"
+ android:divider="@null" />
</FrameLayout>
diff --git a/src/com/android/incallui/Call.java b/src/com/android/incallui/Call.java
index 4a337296..a018de0b 100644
--- a/src/com/android/incallui/Call.java
+++ b/src/com/android/incallui/Call.java
@@ -41,18 +41,20 @@ public final class Call {
/* Defines different states of this call */
public static class State {
public static final int INVALID = 0;
- public static final int IDLE = 1; /* The call is idle. Nothing active */
- public static final int ACTIVE = 2; /* There is an active call */
- public static final int INCOMING = 3; /* A normal incoming phone call */
- public static final int CALL_WAITING = 4; /* Incoming call while another is active */
- public static final int DIALING = 5; /* An outgoing call during dial phase */
- public static final int REDIALING = 6; /* Subsequent dialing attempt after a failure */
- public static final int ONHOLD = 7; /* An active phone call placed on hold */
- public static final int DISCONNECTING = 8; /* A call is being ended. */
- public static final int DISCONNECTED = 9; /* State after a call disconnects */
- public static final int CONFERENCED = 10; /* Call part of a conference call */
- public static final int PRE_DIAL_WAIT = 11; /* Waiting for user before outgoing call */
- public static final int CONNECTING = 12; /* Waiting for Telecomm broadcast to finish */
+ public static final int NEW = 1; /* The call is new. */
+ public static final int IDLE = 2; /* The call is idle. Nothing active */
+ public static final int ACTIVE = 3; /* There is an active call */
+ public static final int INCOMING = 4; /* A normal incoming phone call */
+ public static final int CALL_WAITING = 5; /* Incoming call while another is active */
+ public static final int DIALING = 6; /* An outgoing call during dial phase */
+ public static final int REDIALING = 7; /* Subsequent dialing attempt after a failure */
+ public static final int ONHOLD = 8; /* An active phone call placed on hold */
+ public static final int DISCONNECTING = 9; /* A call is being ended. */
+ public static final int DISCONNECTED = 10; /* State after a call disconnects */
+ public static final int CONFERENCED = 11; /* Call part of a conference call */
+ public static final int PRE_DIAL_WAIT = 12; /* Waiting for user before outgoing call */
+ public static final int CONNECTING = 13; /* Waiting for Telecomm broadcast to finish */
+
public static boolean isConnectingOrConnected(int state) {
switch(state) {
@@ -78,6 +80,8 @@ public final class Call {
switch (state) {
case INVALID:
return "INVALID";
+ case NEW:
+ return "NEW";
case IDLE:
return "IDLE";
case ACTIVE:
@@ -246,12 +250,13 @@ public final class Call {
private static int translateState(int state) {
switch (state) {
+ case android.telecom.Call.STATE_NEW:
+ return Call.State.NEW;
case android.telecom.Call.STATE_CONNECTING:
return Call.State.CONNECTING;
case android.telecom.Call.STATE_PRE_DIAL_WAIT:
return Call.State.PRE_DIAL_WAIT;
case android.telecom.Call.STATE_DIALING:
- case android.telecom.Call.STATE_NEW:
return Call.State.DIALING;
case android.telecom.Call.STATE_RINGING:
return Call.State.INCOMING;
@@ -495,13 +500,14 @@ public final class Call {
public String toString() {
return String.format(Locale.US,
"[%s, %s, %s, children:%s, parent:%s, videoState:%d, mIsActivSub:%b,"
- + " " + "callSubState:%d, mSessionModificationState:%d]",
+ + " " + "callSubState:%d, mSessionModificationState:%d, conferenceable:%s]",
mId,
State.toString(getState()),
PhoneCapabilities.toString(mTelecommCall.getDetails().getCallCapabilities()),
mChildCallIds,
getParentId(),
mTelecommCall.getDetails().getVideoState(), mIsActiveSub,
- mTelecommCall.getDetails().getCallSubstate(), mSessionModificationState);
+ mTelecommCall.getDetails().getCallSubstate(), mSessionModificationState,
+ this.mTelecommCall.getConferenceableCalls());
}
}
diff --git a/src/com/android/incallui/CallButtonPresenter.java b/src/com/android/incallui/CallButtonPresenter.java
index 6079dbe5..ec39ca4b 100644
--- a/src/com/android/incallui/CallButtonPresenter.java
+++ b/src/com/android/incallui/CallButtonPresenter.java
@@ -24,6 +24,7 @@ import android.telecom.PhoneCapabilities;
import android.telecom.VideoProfile;
import com.android.incallui.AudioModeProvider.AudioModeListener;
+import com.android.incallui.InCallPresenter.CanAddCallListener;
import com.android.incallui.InCallPresenter.InCallState;
import com.android.incallui.InCallPresenter.InCallStateListener;
import com.android.incallui.InCallPresenter.IncomingCallListener;
@@ -40,7 +41,7 @@ import java.util.Objects;
*/
public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButtonUi>
implements InCallStateListener, AudioModeListener, IncomingCallListener,
- InCallDetailsListener, CallList.ActiveSubChangeListener {
+ InCallDetailsListener, CallList.ActiveSubChangeListener, CanAddCallListener {
private Call mCall;
private boolean mAutomaticallyMuted = false;
@@ -60,6 +61,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
InCallPresenter.getInstance().addIncomingCallListener(this);
InCallPresenter.getInstance().addDetailsListener(this);
CallList.getInstance().addActiveSubChangeListener(this);
+ InCallPresenter.getInstance().addCanAddCallListener(this);
}
@Override
@@ -114,12 +116,9 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
*/
@Override
public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
- // If the details change is not for the currently active call no update is required.
- if (!Objects.equals(call, mCall)) {
- return;
+ if (getUi() != null && Objects.equals(call, mCall)) {
+ updateCallButtons(call, getUi().getContext());
}
-
- updateCallButtons(call, getUi().getContext());
}
@Override
@@ -128,6 +127,13 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
}
@Override
+ public void onCanAddCallChanged(boolean canAddCall) {
+ if (getUi() != null && mCall != null) {
+ updateCallButtons(mCall, getUi().getContext());
+ }
+ }
+
+ @Override
public void onAudioMode(int mode) {
if (getUi() != null) {
getUi().setAudio(mode);
@@ -378,12 +384,12 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
Log.v(this, "Enable hold", call.can(PhoneCapabilities.HOLD));
Log.v(this, "Show merge ", call.can(PhoneCapabilities.MERGE_CONFERENCE));
Log.v(this, "Show swap ", call.can(PhoneCapabilities.SWAP_CONFERENCE));
- Log.v(this, "Show add call ", call.can(PhoneCapabilities.ADD_CALL));
+ Log.v(this, "Show add call ", TelecomAdapter.getInstance().canAddCall());
Log.v(this, "Show mute ", call.can(PhoneCapabilities.MUTE));
Log.v(this, "Show video call local:", call.can(PhoneCapabilities.SUPPORTS_VT_LOCAL)
+ " remote: " + call.can(PhoneCapabilities.SUPPORTS_VT_REMOTE));
- final boolean canAdd = call.can(PhoneCapabilities.ADD_CALL);
+ final boolean canAdd = TelecomAdapter.getInstance().canAddCall();
final boolean enableHoldOption = call.can(PhoneCapabilities.HOLD);
final boolean supportHold = call.can(PhoneCapabilities.SUPPORT_HOLD);
@@ -406,7 +412,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
final boolean isVideoOverflowScenario = canVideoCall
&& (showAddCallOption || showMergeOption) && (showHoldOption || showSwapOption);
// If we show hold/swap, add, and merge simultaneously, the overflow menu is needed.
- final boolean isCdmaConferenceOverflowScenario =
+ final boolean isOverflowScenario =
(showHoldOption || showSwapOption) && showMergeOption && showAddCallOption;
if (isVideoOverflowScenario) {
@@ -415,14 +421,14 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
ui.showAddCallButton(false);
ui.showMergeButton(false);
- ui.showOverflowButton(true);
ui.configureOverflowMenu(
showMergeOption,
showAddCallOption /* showAddMenuOption */,
showHoldOption && enableHoldOption /* showHoldMenuOption */,
showSwapOption);
+ ui.showOverflowButton(true);
} else {
- if (isCdmaConferenceOverflowScenario) {
+ if (isOverflowScenario) {
ui.showAddCallButton(false);
ui.showMergeButton(false);
@@ -438,6 +444,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto
PhoneCapabilities.ADD_PARTICIPANT));
}
+ ui.showOverflowButton(isOverflowScenario);
ui.showHoldButton(showHoldOption);
ui.enableHold(enableHoldOption);
ui.showSwapButton(showSwapOption);
diff --git a/src/com/android/incallui/CallCardFragment.java b/src/com/android/incallui/CallCardFragment.java
index cee37d89..7875e121 100644
--- a/src/com/android/incallui/CallCardFragment.java
+++ b/src/com/android/incallui/CallCardFragment.java
@@ -493,17 +493,9 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
@Override
public void setPrimary(String number, String name, boolean nameIsNumber, String label,
- Drawable photo, boolean isConference, boolean canManageConference,
- boolean isSipCall, boolean isForwarded) {
+ Drawable photo, boolean isSipCall, boolean isForwarded) {
Log.d(this, "Setting primary call");
- if (isConference) {
- name = getConferenceString(canManageConference);
- photo = getConferencePhoto(canManageConference);
- photo.setAutoMirrored(true);
- nameIsNumber = false;
- }
-
// set the name field.
setPrimaryName(name, nameIsNumber);
@@ -525,8 +517,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
@Override
public void setSecondary(boolean show, String name, boolean nameIsNumber, String label,
- String providerLabel, Drawable providerIcon, boolean isConference,
- boolean canManageConference) {
+ String providerLabel, Drawable providerIcon, boolean isConference) {
if (show != mSecondaryCallInfo.isShown()) {
updateFabPositionForSecondaryCallInfo();
@@ -536,13 +527,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
boolean hasProvider = !TextUtils.isEmpty(providerLabel);
showAndInitializeSecondaryCallInfo(hasProvider);
- if (isConference) {
- name = getConferenceString(canManageConference);
- nameIsNumber = false;
- mSecondaryCallConferenceCallIcon.setVisibility(View.VISIBLE);
- } else {
- mSecondaryCallConferenceCallIcon.setVisibility(View.GONE);
- }
+ mSecondaryCallConferenceCallIcon.setVisibility(isConference ? View.VISIBLE : View.GONE);
mSecondaryCallName.setText(name);
if (hasProvider) {
@@ -702,19 +687,6 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
}
}
- private String getConferenceString(boolean canManageConference) {
- Log.v(this, "canManageConferenceString: " + canManageConference);
- final int resId = canManageConference
- ? R.string.card_title_conf_call : R.string.card_title_in_call;
- return getView().getResources().getString(resId);
- }
-
- private Drawable getConferencePhoto(boolean canManageConference) {
- Log.v(this, "canManageConferencePhoto: " + canManageConference);
- final int resId = canManageConference ? R.drawable.img_conference : R.drawable.img_phone;
- return getView().getResources().getDrawable(resId);
- }
-
/**
* Gets the call state label based on the state of the call or cause of disconnect.
*
@@ -892,6 +864,16 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
mManageConferenceCallButton.setVisibility(visible ? View.VISIBLE : View.GONE);
}
+ /**
+ * Determines the current visibility of the manage conference button.
+ *
+ * @return {@code true} if the button is visible.
+ */
+ @Override
+ public boolean isManageConferenceVisible() {
+ return mManageConferenceCallButton.getVisibility() == View.VISIBLE;
+ }
+
private void dispatchPopulateAccessibilityEvent(AccessibilityEvent event, View view) {
if (view == null) return;
final List<CharSequence> eventText = event.getText();
diff --git a/src/com/android/incallui/CallCardPresenter.java b/src/com/android/incallui/CallCardPresenter.java
index c142e2d0..8a4e8a9a 100644
--- a/src/com/android/incallui/CallCardPresenter.java
+++ b/src/com/android/incallui/CallCardPresenter.java
@@ -26,7 +26,6 @@ import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
import android.telecom.DisconnectCause;
import android.telecom.PhoneCapabilities;
import android.telecom.PhoneAccount;
@@ -127,7 +126,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
if (!call.isConferenceCall()) {
startContactInfoSearch(call, true, call.getState() == Call.State.INCOMING);
} else {
- updateContactEntry(null, true, true);
+ updateContactEntry(null, true);
}
}
}
@@ -138,7 +137,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
// Contact search may have completed before ui is ready.
if (mPrimaryContactInfo != null) {
- updatePrimaryDisplayInfo(mPrimaryContactInfo, isConference(mPrimary));
+ updatePrimaryDisplayInfo();
}
// Register for call state changes last
@@ -206,26 +205,30 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
mSecondary = secondary;
mPrimary = primary;
- if (primaryChanged && mPrimary != null) {
+ // Refresh primary call information if either:
+ // 1. Primary call changed.
+ // 2. The call's ability to manage conference has changed.
+ if (mPrimary != null && (primaryChanged ||
+ ui.isManageConferenceVisible() != shouldShowManageConference())) {
// primary call has changed
mPrimaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(mContext, mPrimary,
mPrimary.getState() == Call.State.INCOMING);
- updatePrimaryDisplayInfo(mPrimaryContactInfo, isConference(mPrimary));
+ updatePrimaryDisplayInfo();
maybeStartSearch(mPrimary, true);
mPrimary.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
} else if (primaryForwardedChanged && mPrimary != null) {
- updatePrimaryDisplayInfo(mPrimaryContactInfo, isConference(mPrimary));
+ updatePrimaryDisplayInfo();
}
if (mSecondary == null) {
// Secondary call may have ended. Update the ui.
mSecondaryContactInfo = null;
- updateSecondaryDisplayInfo(false);
+ updateSecondaryDisplayInfo();
} else if (secondaryChanged) {
// secondary call has changed
mSecondaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(mContext, mSecondary,
mSecondary.getState() == Call.State.INCOMING);
- updateSecondaryDisplayInfo(mSecondary.isConferenceCall());
+ updateSecondaryDisplayInfo();
maybeStartSearch(mSecondary, false);
mSecondary.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
}
@@ -277,6 +280,11 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
@Override
public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
updatePrimaryCallState();
+
+ if (call.can(PhoneCapabilities.MANAGE_CONFERENCE) != PhoneCapabilities.can(
+ details.getCallCapabilities(), PhoneCapabilities.MANAGE_CONFERENCE)) {
+ maybeShowManageConferenceCallButton();
+ }
}
private String getSubscriptionNumber() {
@@ -314,13 +322,21 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
* Only show the conference call button if we can manage the conference.
*/
private void maybeShowManageConferenceCallButton() {
+ getUi().showManageConferenceCallButton(shouldShowManageConference());
+ }
+
+ /**
+ * Determines if the manage conference button should be visible, based on the current primary
+ * call.
+ *
+ * @return {@code True} if the manage conference button should be visible.
+ */
+ private boolean shouldShowManageConference() {
if (mPrimary == null) {
- getUi().showManageConferenceCallButton(false);
- return;
+ return false;
}
- final boolean canManageConference = mPrimary.can(PhoneCapabilities.MANAGE_CONFERENCE);
- getUi().showManageConferenceCallButton(mPrimary.isConferenceCall() && canManageConference);
+ return mPrimary.can(PhoneCapabilities.MANAGE_CONFERENCE);
}
private void setCallbackNumber() {
@@ -392,7 +408,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
}
private void onContactInfoComplete(String callId, ContactCacheEntry entry, boolean isPrimary) {
- updateContactEntry(entry, isPrimary, false);
+ updateContactEntry(entry, isPrimary);
if (entry.name != null) {
Log.d(TAG, "Contact found: " + entry);
}
@@ -413,26 +429,17 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
}
}
- private static boolean isConference(Call call) {
- return call != null && call.isConferenceCall();
- }
-
- private static boolean canManageConference(Call call) {
- return call != null && call.can(PhoneCapabilities.MANAGE_CONFERENCE);
- }
-
private static boolean isForwarded(Call call) {
return call != null && call.isForwarded();
}
- private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary,
- boolean isConference) {
+ private void updateContactEntry(ContactCacheEntry entry, boolean isPrimary) {
if (isPrimary) {
mPrimaryContactInfo = entry;
- updatePrimaryDisplayInfo(entry, isConference);
+ updatePrimaryDisplayInfo();
} else {
mSecondaryContactInfo = entry;
- updateSecondaryDisplayInfo(isConference);
+ updateSecondaryDisplayInfo();
}
}
@@ -478,8 +485,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
return retval;
}
- private void updatePrimaryDisplayInfo(ContactCacheEntry entry, boolean isConference) {
- Log.d(TAG, "Update primary display " + entry);
+ private void updatePrimaryDisplayInfo() {
final CallCardUi ui = getUi();
if (ui == null) {
// TODO: May also occur if search result comes back after ui is destroyed. Look into
@@ -488,21 +494,41 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
return;
}
- final boolean canManageConference = canManageConference(mPrimary);
+ if (mPrimary == null) {
+ // Clear the primary display info.
+ ui.setPrimary(null, null, false, null, null, false, false);
+ return;
+ }
+
final boolean isForwarded = isForwarded(mPrimary);
- if (entry != null && mPrimary != null) {
- final String name = getNameForCall(entry);
- final String number = getNumberForCall(entry);
- final boolean nameIsNumber = name != null && name.equals(entry.number);
- boolean isIncoming = mPrimary.getState() == Call.State.INCOMING;
- final String checkIdpName = checkIdp(name, nameIsNumber, isIncoming);
-
- ui.setPrimary(number, checkIdpName, nameIsNumber, entry.label,
- entry.photo, isConference, canManageConference,
- entry.isSipCall, isForwarded);
+ if (mPrimary.isConferenceCall()) {
+ Log.d(TAG, "Update primary display info for conference call.");
+
+ ui.setPrimary(
+ null /* number */,
+ getConferenceString(mPrimary),
+ false /* nameIsNumber */,
+ null /* label */,
+ getConferencePhoto(mPrimary),
+ false /* isSipCall */,
+ isForwarded);
+ } else if (mPrimaryContactInfo != null) {
+ Log.d(TAG, "Update primary display info for " + mPrimaryContactInfo);
+
+ String name = getNameForCall(mPrimaryContactInfo);
+ String number = getNumberForCall(mPrimaryContactInfo);
+ boolean nameIsNumber = name != null && name.equals(mPrimaryContactInfo.number);
+ ui.setPrimary(
+ number,
+ name,
+ nameIsNumber,
+ mPrimaryContactInfo.label,
+ mPrimaryContactInfo.photo,
+ mPrimaryContactInfo.isSipCall,
+ isForwarded);
} else {
- ui.setPrimary(null, null, false, null, null, isConference,
- canManageConference, false, isForwarded);
+ // Clear the primary display info.
+ ui.setPrimary(null, null, false, null, null, false, false);
}
}
@@ -537,25 +563,42 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
}
}
- private void updateSecondaryDisplayInfo(boolean isConference) {
+ private void updateSecondaryDisplayInfo() {
final CallCardUi ui = getUi();
if (ui == null) {
return;
}
- final boolean canManageConference = canManageConference(mSecondary);
- if (mSecondaryContactInfo != null && mSecondary != null) {
- Log.d(TAG, "updateSecondaryDisplayInfo() " + mSecondaryContactInfo);
- final String nameForCall = getNameForCall(mSecondaryContactInfo);
+ if (mSecondary == null) {
+ // Clear the secondary display info.
+ ui.setSecondary(false, null, false, null, null, null, false /* isConference */);
+ return;
+ }
- final boolean nameIsNumber = nameForCall != null && nameForCall.equals(
- mSecondaryContactInfo.number);
- ui.setSecondary(true /* show */, nameForCall, nameIsNumber, mSecondaryContactInfo.label,
- getCallProviderLabel(mSecondary), getCallProviderIcon(mSecondary),
- isConference, canManageConference);
+ if (mSecondary.isConferenceCall()) {
+ ui.setSecondary(
+ true /* show */,
+ getConferenceString(mSecondary),
+ false /* nameIsNumber */,
+ null /* label */,
+ getCallProviderLabel(mSecondary),
+ getCallProviderIcon(mSecondary),
+ true /* isConference */);
+ } else if (mSecondaryContactInfo != null) {
+ Log.d(TAG, "updateSecondaryDisplayInfo() " + mSecondaryContactInfo);
+ String name = getNameForCall(mSecondaryContactInfo);
+ boolean nameIsNumber = name != null && name.equals(mSecondaryContactInfo.number);
+ ui.setSecondary(
+ true /* show */,
+ name,
+ nameIsNumber,
+ mSecondaryContactInfo.label,
+ getCallProviderLabel(mSecondary),
+ getCallProviderIcon(mSecondary),
+ false /* isConference */);
} else {
- // reset to nothing so that it starts off blank next time we use it.
- ui.setSecondary(false, null, false, null, null, null, isConference, canManageConference);
+ // Clear the secondary display info.
+ ui.setSecondary(false, null, false, null, null, null, false /* isConference */);
}
}
@@ -745,15 +788,41 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
.show();
}
+ private TelecomManager getTelecomManager() {
+ if (mTelecomManager == null) {
+ mTelecomManager =
+ (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+ }
+ return mTelecomManager;
+ }
+
+ private String getConferenceString(Call call) {
+ boolean isGenericConference = call.can(PhoneCapabilities.GENERIC_CONFERENCE);
+ Log.v(this, "getConferenceString: " + isGenericConference);
+
+ final int resId = isGenericConference
+ ? R.string.card_title_in_call : R.string.card_title_conf_call;
+ return mContext.getResources().getString(resId);
+ }
+
+ private Drawable getConferencePhoto(Call call) {
+ boolean isGenericConference = call.can(PhoneCapabilities.GENERIC_CONFERENCE);
+ Log.v(this, "getConferencePhoto: " + isGenericConference);
+
+ final int resId = isGenericConference
+ ? R.drawable.img_phone : R.drawable.img_conference;
+ Drawable photo = mContext.getResources().getDrawable(resId);
+ photo.setAutoMirrored(true);
+ return photo;
+ }
+
public interface CallCardUi extends Ui {
void setVisible(boolean on);
void setCallCardVisible(boolean visible);
void setPrimary(String number, String name, boolean nameIsNumber, String label,
- Drawable photo, boolean isConference, boolean canManageConference,
- boolean isSipCall, boolean isForwarded);
+ Drawable photo, boolean isSipCall, boolean isForwarded);
void setSecondary(boolean show, String name, boolean nameIsNumber, String label,
- String providerLabel, Drawable providerIcon, boolean isConference,
- boolean canManageConference);
+ String providerLabel, Drawable providerIcon, boolean isConference);
void setCallState(int state, int videoState, int sessionModificationState,
DisconnectCause disconnectCause, String connectionLabel,
Drawable connectionIcon, String gatewayNumber, boolean isWaitingForRemoteSide);
@@ -767,14 +836,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
void setPhotoVisible(boolean isVisible);
void setProgressSpinnerVisible(boolean visible);
void showManageConferenceCallButton(boolean visible);
- }
-
- private TelecomManager getTelecomManager() {
- if (mTelecomManager == null) {
- mTelecomManager =
- (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
- }
- return mTelecomManager;
+ boolean isManageConferenceVisible();
}
public long getActiveSubscription() {
diff --git a/src/com/android/incallui/CallList.java b/src/com/android/incallui/CallList.java
index 6508bcb9..e5c0d385 100644
--- a/src/com/android/incallui/CallList.java
+++ b/src/com/android/incallui/CallList.java
@@ -18,7 +18,6 @@ package com.android.incallui;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
import com.google.common.base.Preconditions;
import android.os.Handler;
diff --git a/src/com/android/incallui/ConferenceManagerFragment.java b/src/com/android/incallui/ConferenceManagerFragment.java
index 8a7dfd21..efdea28b 100644
--- a/src/com/android/incallui/ConferenceManagerFragment.java
+++ b/src/com/android/incallui/ConferenceManagerFragment.java
@@ -17,18 +17,16 @@
package com.android.incallui;
import android.app.ActionBar;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
+import android.content.Context;
import android.os.Bundle;
-import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
+import android.widget.ListView;
import com.android.contacts.common.ContactPhotoManager;
-import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
+
+import java.util.List;
/**
* Fragment for call control buttons
@@ -38,9 +36,11 @@ public class ConferenceManagerFragment
ConferenceManagerPresenter.ConferenceManagerUi>
implements ConferenceManagerPresenter.ConferenceManagerUi {
- private ViewGroup[] mConferenceCallList;
+ private ListView mConferenceParticipantList;
private int mActionBarElevation;
private ContactPhotoManager mContactPhotoManager;
+ private LayoutInflater mInflater;
+ private ConferenceParticipantListAdapter mConferenceParticipantListAdapter;
@Override
ConferenceManagerPresenter createPresenter() {
@@ -61,22 +61,15 @@ public class ConferenceManagerFragment
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- final View parent = inflater.inflate(R.layout.conference_manager_fragment, container,
- false);
-
- // Create list of conference call widgets
- mConferenceCallList = new ViewGroup[getPresenter().getMaxCallersInConference()];
- final int[] viewGroupIdList = { R.id.caller0, R.id.caller1, R.id.caller2,
- R.id.caller3, R.id.caller4 };
- for (int i = 0; i < getPresenter().getMaxCallersInConference(); i++) {
- mConferenceCallList[i] = (ViewGroup) parent.findViewById(viewGroupIdList[i]);
- }
+ final View parent =
+ inflater.inflate(R.layout.conference_manager_fragment, container, false);
+ mConferenceParticipantList = (ListView) parent.findViewById(R.id.participantList);
mContactPhotoManager =
ContactPhotoManager.getInstance(getActivity().getApplicationContext());
-
mActionBarElevation =
(int) getResources().getDimension(R.dimen.incall_action_bar_elevation);
+ mInflater = LayoutInflater.from(getActivity().getApplicationContext());
return parent;
}
@@ -117,83 +110,18 @@ public class ConferenceManagerFragment
}
@Override
- public void setRowVisible(int rowId, boolean on) {
- if (on) {
- mConferenceCallList[rowId].setVisibility(View.VISIBLE);
- } else {
- mConferenceCallList[rowId].setVisibility(View.GONE);
- }
- }
-
- /**
- * Helper function to fill out the Conference Call(er) information
- * for each item in the "Manage Conference Call" list.
- */
- @Override
- public final void displayCallerInfoForConferenceRow(int rowId, String callerName,
- String callerNumber, String callerNumberType, String lookupKey, Uri photoUri) {
-
- final ImageView photoView = (ImageView) mConferenceCallList[rowId].findViewById(
- R.id.callerPhoto);
- final TextView nameTextView = (TextView) mConferenceCallList[rowId].findViewById(
- R.id.conferenceCallerName);
- final TextView numberTextView = (TextView) mConferenceCallList[rowId].findViewById(
- R.id.conferenceCallerNumber);
- final TextView numberTypeTextView = (TextView) mConferenceCallList[rowId].findViewById(
- R.id.conferenceCallerNumberType);
-
- DefaultImageRequest imageRequest = (photoUri != null) ? null :
- new DefaultImageRequest(callerName, lookupKey, true /* isCircularPhoto */);
- mContactPhotoManager.loadDirectoryPhoto(photoView, photoUri, null, false, true, imageRequest);
-
- // set the caller name
- nameTextView.setText(callerName);
-
- // set the caller number in subscript, or make the field disappear.
- if (TextUtils.isEmpty(callerNumber)) {
- numberTextView.setVisibility(View.GONE);
- numberTypeTextView.setVisibility(View.GONE);
- } else {
- numberTextView.setVisibility(View.VISIBLE);
- numberTextView.setText(callerNumber);
- numberTypeTextView.setVisibility(View.VISIBLE);
- numberTypeTextView.setText(callerNumberType);
- }
- }
+ public void update(Context context, List<Call> participants, boolean parentCanSeparate) {
+ if (mConferenceParticipantListAdapter == null) {
+ mConferenceParticipantListAdapter = new ConferenceParticipantListAdapter(
+ mConferenceParticipantList, context, mInflater, mContactPhotoManager);
- @Override
- public final void setupEndButtonForRow(final int rowId, boolean canDisconnect) {
- View endButton = mConferenceCallList[rowId].findViewById(R.id.conferenceCallerDisconnect);
-
- // Comment
- if (canDisconnect) {
- endButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- getPresenter().endConferenceConnection(rowId);
- }
- });
- endButton.setVisibility(View.VISIBLE);
- } else {
- endButton.setVisibility(View.INVISIBLE);
+ mConferenceParticipantList.setAdapter(mConferenceParticipantListAdapter);
}
+ mConferenceParticipantListAdapter.updateParticipants(participants, parentCanSeparate);
}
@Override
- public final void setupSeparateButtonForRow(final int rowId, boolean canSeparate) {
- final View separateButton =
- mConferenceCallList[rowId].findViewById(R.id.conferenceCallerSeparate);
-
- if (canSeparate) {
- separateButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- getPresenter().separateConferenceConnection(rowId);
- }
- });
- separateButton.setVisibility(View.VISIBLE);
- } else {
- separateButton.setVisibility(View.INVISIBLE);
- }
+ public void refreshCall(Call call) {
+ mConferenceParticipantListAdapter.refreshCall(call);
}
}
diff --git a/src/com/android/incallui/ConferenceManagerPresenter.java b/src/com/android/incallui/ConferenceManagerPresenter.java
index 269f37d9..7acd94e3 100644
--- a/src/com/android/incallui/ConferenceManagerPresenter.java
+++ b/src/com/android/incallui/ConferenceManagerPresenter.java
@@ -22,21 +22,22 @@ import android.telecom.PhoneCapabilities;
import android.text.TextUtils;
import com.android.incallui.ContactInfoCache.ContactCacheEntry;
+import com.android.incallui.InCallPresenter.InCallDetailsListener;
import com.android.incallui.InCallPresenter.InCallState;
import com.android.incallui.InCallPresenter.InCallStateListener;
import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Logic for call buttons.
*/
public class ConferenceManagerPresenter
extends Presenter<ConferenceManagerPresenter.ConferenceManagerUi>
- implements InCallStateListener {
-
- private static final int MAX_CALLERS_IN_CONFERENCE = 5;
+ implements InCallStateListener, InCallDetailsListener {
- private String[] mCallerIds;
private Context mContext;
@Override
@@ -73,22 +74,48 @@ public class ConferenceManagerPresenter
}
}
+ @Override
+ public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
+ boolean canDisconnect = PhoneCapabilities.can(
+ details.getCallCapabilities(), PhoneCapabilities.DISCONNECT_FROM_CONFERENCE);
+ boolean canSeparate = PhoneCapabilities.can(
+ details.getCallCapabilities(), PhoneCapabilities.SEPARATE_FROM_CONFERENCE);
+
+ if (call.can(PhoneCapabilities.DISCONNECT_FROM_CONFERENCE) != canDisconnect
+ || call.can(PhoneCapabilities.SEPARATE_FROM_CONFERENCE) != canSeparate) {
+ getUi().refreshCall(call);
+ }
+
+ if (!PhoneCapabilities.can(
+ details.getCallCapabilities(), PhoneCapabilities.MANAGE_CONFERENCE)) {
+ getUi().setVisible(false);
+ }
+ }
+
public void init(Context context, CallList callList) {
mContext = Preconditions.checkNotNull(context);
mContext = context;
update(callList);
}
+ /**
+ * Updates the conference participant adapter.
+ *
+ * @param callList The callList.
+ */
private void update(CallList callList) {
// callList is non null, but getActiveOrBackgroundCall() may return null
final Call currentCall = callList.getActiveOrBackgroundCall();
- if (currentCall != null) {
- // getChildCallIds() always returns a valid Set
- mCallerIds = currentCall.getChildCallIds().toArray(new String[0]);
- } else {
- mCallerIds = new String[0];
+ if (currentCall == null) {
+ return;
+ }
+
+ ArrayList<Call> calls = new ArrayList<>(currentCall.getChildCallIds().size());
+ for (String callerId : currentCall.getChildCallIds()) {
+ calls.add(callList.getCallById(callerId));
}
- Log.d(this, "Number of calls is " + String.valueOf(mCallerIds.length));
+
+ Log.d(this, "Number of calls is " + String.valueOf(calls.size()));
// Users can split out a call from the conference call if there either the active call
// or the holding call is empty. If both are filled at the moment, users can not split out
@@ -97,92 +124,13 @@ public class ConferenceManagerPresenter
final boolean hasHoldingCall = (callList.getBackgroundCall() != null);
boolean canSeparate = !(hasActiveCall && hasHoldingCall);
- for (int i = 0; i < MAX_CALLERS_IN_CONFERENCE; i++) {
- if (i < mCallerIds.length) {
- int callCapabilities =
- callList.getCallById(currentCall.getChildCallIds().get(i))
- .getTelecommCall().getDetails().getCallCapabilities();
- boolean thisRowCanSeparate = canSeparate &&
- ((callCapabilities & PhoneCapabilities.SEPARATE_FROM_CONFERENCE) != 0);
- boolean thisRowCanDisconnect =
- ((callCapabilities & PhoneCapabilities.DISCONNECT_FROM_CONFERENCE) != 0);
- // Fill in the row in the UI for this caller.
- final ContactCacheEntry contactCache = ContactInfoCache.getInstance(mContext).
- getInfo(mCallerIds[i]);
- updateManageConferenceRow(
- i,
- contactCache,
- thisRowCanSeparate,
- thisRowCanDisconnect);
- } else {
- // Blank out this row in the UI
- updateManageConferenceRow(i, null, false, false);
- }
- }
- }
-
- /**
- * Updates a single row of the "Manage conference" UI. (One row in this
- * UI represents a single caller in the conference.)
- *
- * @param i the row to update
- * @param contactCacheEntry the contact details corresponding to this caller.
- * If null, that means this is an "empty slot" in the conference,
- * so hide this row in the UI.
- * @param canSeparate if true, show a "Separate" (i.e. "Private") button
- * on this row in the UI.
- * @param canDisconnect if true, show a "Disconnect" button on this row in the UI.
- */
- public void updateManageConferenceRow(final int i,
- final ContactCacheEntry contactCacheEntry,
- boolean canSeparate,
- boolean canDisconnect) {
-
- if (contactCacheEntry != null) {
- // Activate this row of the Manage conference panel:
- getUi().setRowVisible(i, true);
-
- String name = contactCacheEntry.name;
- String number = contactCacheEntry.number;
-
- if (TextUtils.isEmpty(name)) {
- name = number;
- number = null;
- }
-
- getUi().setupSeparateButtonForRow(i, canSeparate);
- getUi().setupEndButtonForRow(i, canDisconnect);
- getUi().displayCallerInfoForConferenceRow(i, name, number, contactCacheEntry.label,
- contactCacheEntry.lookupKey, contactCacheEntry.displayPhotoUri);
- } else {
- // Disable this row of the Manage conference panel:
- getUi().setRowVisible(i, false);
- }
- }
-
- public int getMaxCallersInConference() {
- return MAX_CALLERS_IN_CONFERENCE;
- }
-
- public void separateConferenceConnection(int rowId) {
- if (rowId < mCallerIds.length) {
- TelecomAdapter.getInstance().separateCall(mCallerIds[rowId]);
- }
- }
-
- public void endConferenceConnection(int rowId) {
- if (rowId < mCallerIds.length) {
- TelecomAdapter.getInstance().disconnectCall(mCallerIds[rowId]);
- }
+ getUi().update(mContext, calls, canSeparate);
}
public interface ConferenceManagerUi extends Ui {
void setVisible(boolean on);
boolean isFragmentVisible();
- void setRowVisible(int rowId, boolean on);
- void displayCallerInfoForConferenceRow(int rowId, String callerName, String callerNumber,
- String callerNumberType, String lookupKey, Uri photoUri);
- void setupSeparateButtonForRow(int rowId, boolean canSeparate);
- void setupEndButtonForRow(int rowId, boolean canDisconnect);
+ void update(Context context, List<Call> participants, boolean parentCanSeparate);
+ void refreshCall(Call call);
}
}
diff --git a/src/com/android/incallui/ConferenceParticipantListAdapter.java b/src/com/android/incallui/ConferenceParticipantListAdapter.java
new file mode 100644
index 00000000..641261e0
--- /dev/null
+++ b/src/com/android/incallui/ConferenceParticipantListAdapter.java
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.incallui;
+
+import android.content.Context;
+import android.net.Uri;
+import android.telecom.PhoneCapabilities;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.contacts.common.ContactPhotoManager;
+import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
+import com.android.incallui.ContactInfoCache.ContactCacheEntry;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Adapter for a ListView containing conference call participant information.
+ */
+public class ConferenceParticipantListAdapter extends BaseAdapter {
+
+ /**
+ * Internal class which represents a participant. Includes a reference to the {@link Call} and
+ * the corresponding {@link ContactCacheEntry} for the participant.
+ */
+ private class ParticipantInfo {
+ private Call mCall;
+ private ContactCacheEntry mContactCacheEntry;
+ private boolean mCacheLookupComplete = false;
+
+ public ParticipantInfo(Call call, ContactCacheEntry contactCacheEntry) {
+ mCall = call;
+ mContactCacheEntry = contactCacheEntry;
+ }
+
+ public Call getCall() {
+ return mCall;
+ }
+
+ public void setCall(Call call) {
+ mCall = call;
+ }
+
+ public ContactCacheEntry getContactCacheEntry() {
+ return mContactCacheEntry;
+ }
+
+ public void setContactCacheEntry(ContactCacheEntry entry) {
+ mContactCacheEntry = entry;
+ }
+
+ public boolean isCacheLookupComplete() {
+ return mCacheLookupComplete;
+ }
+
+ public void setCacheLookupComplete(boolean cacheLookupComplete) {
+ mCacheLookupComplete = cacheLookupComplete;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof ParticipantInfo) {
+ ParticipantInfo p = (ParticipantInfo) o;
+ return
+ Objects.equals(p.getCall().getId(), mCall.getId());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mCall.getId().hashCode();
+ }
+ }
+
+ /**
+ * Callback class used when making requests to the {@link ContactInfoCache} to resolve contact
+ * info and contact photos for conference participants.
+ */
+ public static class ContactLookupCallback implements ContactInfoCache.ContactInfoCacheCallback {
+ private final WeakReference<ConferenceParticipantListAdapter> mListAdapter;
+
+ public ContactLookupCallback(ConferenceParticipantListAdapter listAdapter) {
+ mListAdapter = new WeakReference<ConferenceParticipantListAdapter>(listAdapter);
+ }
+
+ /**
+ * Called when contact info has been resolved.
+ *
+ * @param callId The call id.
+ * @param entry The new contact information.
+ */
+ @Override
+ public void onContactInfoComplete(String callId, ContactCacheEntry entry) {
+ update(callId, entry);
+ }
+
+ /**
+ * Called when contact photo has been loaded into the cache.
+ *
+ * @param callId The call id.
+ * @param entry The new contact information.
+ */
+ @Override
+ public void onImageLoadComplete(String callId, ContactCacheEntry entry) {
+ update(callId, entry);
+ }
+
+ /**
+ * Updates the contact information for a participant.
+ *
+ * @param callId The call id.
+ * @param entry The new contact information.
+ */
+ private void update(String callId, ContactCacheEntry entry) {
+ ConferenceParticipantListAdapter listAdapter = mListAdapter.get();
+ if (listAdapter != null) {
+ listAdapter.updateContactInfo(callId, entry);
+ }
+ }
+ }
+
+ /**
+ * Listener used to handle tap of the "disconnect' button for a participant.
+ */
+ private View.OnClickListener mDisconnectListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ View parent = (View) v.getParent();
+ String callId = (String) parent.getTag();
+ TelecomAdapter.getInstance().disconnectCall(callId);
+ }
+ };
+
+ /**
+ * Listener used to handle tap of the "separate' button for a participant.
+ */
+ private View.OnClickListener mSeparateListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ View parent = (View) v.getParent();
+ String callId = (String) parent.getTag();
+ TelecomAdapter.getInstance().separateCall(callId);
+ }
+ };
+
+ /**
+ * The ListView containing the participant information.
+ */
+ private final ListView mListView;
+
+ /**
+ * The conference participants to show in the ListView.
+ */
+ private List<ParticipantInfo> mConferenceParticipants = new ArrayList<>();
+
+ /**
+ * Hashmap to make accessing participant info by call Id faster.
+ */
+ private final HashMap<String, ParticipantInfo> mParticipantsByCallId = new HashMap<>();
+
+ /**
+ * The context.
+ */
+ private final Context mContext;
+
+ /**
+ * The layout inflater used to inflate new views.
+ */
+ private final LayoutInflater mLayoutInflater;
+
+ /**
+ * Contact photo manager to retrieve cached contact photo information.
+ */
+ private final ContactPhotoManager mContactPhotoManager;
+
+ /**
+ * {@code True} if the conference parent supports separating calls from the conference.
+ */
+ private boolean mParentCanSeparate;
+
+ /**
+ * Creates an instance of the ConferenceParticipantListAdapter.
+ *
+ * @param listView The listview.
+ * @param context The context.
+ * @param layoutInflater The layout inflater.
+ * @param contactPhotoManager The contact photo manager, used to load contact photos.
+ */
+ public ConferenceParticipantListAdapter(ListView listView, Context context,
+ LayoutInflater layoutInflater, ContactPhotoManager contactPhotoManager) {
+
+ mListView = listView;
+ mContext = context;
+ mLayoutInflater = layoutInflater;
+ mContactPhotoManager = contactPhotoManager;
+ }
+
+ /**
+ * Updates the adapter with the new conference participant information provided.
+ *
+ * @param conferenceParticipants The list of conference participants.
+ * @param parentCanSeparate {@code True} if the parent supports separating calls from the
+ * conference.
+ */
+ public void updateParticipants(List<Call> conferenceParticipants, boolean parentCanSeparate) {
+ mParentCanSeparate = parentCanSeparate;
+ updateParticipantInfo(conferenceParticipants);
+ }
+
+ /**
+ * Determines the number of participants in the conference.
+ *
+ * @return The number of participants.
+ */
+ @Override
+ public int getCount() {
+ return mConferenceParticipants.size();
+ }
+
+ /**
+ * Retrieves an item from the list of participants.
+ *
+ * @param position Position of the item whose data we want within the adapter's
+ * data set.
+ * @return The {@link ParticipantInfo}.
+ */
+ @Override
+ public Object getItem(int position) {
+ return mConferenceParticipants.get(position);
+ }
+
+ /**
+ * Retreives the adapter-specific item id for an item at a specified position.
+ *
+ * @param position The position of the item within the adapter's data set whose row id we want.
+ * @return The item id.
+ */
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ /**
+ * Refreshes call information for the call passed in.
+ *
+ * @param call The new call information.
+ */
+ public void refreshCall(Call call) {
+ String callId = call.getId();
+
+ if (mParticipantsByCallId.containsKey(callId)) {
+ ParticipantInfo participantInfo = mParticipantsByCallId.get(callId);
+ participantInfo.setCall(call);
+ refreshView(callId);
+ }
+ }
+
+ /**
+ * Attempts to refresh the view for the specified call ID. This ensures the contact info and
+ * photo loaded from cache are updated.
+ *
+ * @param callId The call id.
+ */
+ private void refreshView(String callId) {
+ int first = mListView.getFirstVisiblePosition();
+ int last = mListView.getLastVisiblePosition();
+
+ for (int position = 0; position <= last - first; position++) {
+ View view = mListView.getChildAt(position);
+ String rowCallId = (String) view.getTag();
+ if (rowCallId.equals(callId)) {
+ getView(position+first, view, mListView);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Creates or populates an existing conference participant row.
+ *
+ * @param position The position of the item within the adapter's data set of the item whose view
+ * we want.
+ * @param convertView The old view to reuse, if possible.
+ * @param parent The parent that this view will eventually be attached to
+ * @return The populated view.
+ */
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ // Make sure we have a valid convertView to start with
+ final View result = convertView == null
+ ? mLayoutInflater.inflate(R.layout.caller_in_conference, parent, false)
+ : convertView;
+
+ ParticipantInfo participantInfo = mConferenceParticipants.get(position);
+ Call call = participantInfo.getCall();
+ ContactCacheEntry contactCache = participantInfo.getContactCacheEntry();
+
+ final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
+
+ // If a cache lookup has not yet been performed to retrieve the contact information and
+ // photo, do it now.
+ if (!participantInfo.isCacheLookupComplete()) {
+ cache.findInfo(participantInfo.getCall(),
+ participantInfo.getCall().getState() == Call.State.INCOMING,
+ new ContactLookupCallback(this));
+ }
+
+ int callCapabilities = call.getTelecommCall().getDetails().getCallCapabilities();
+ boolean thisRowCanSeparate = mParentCanSeparate && PhoneCapabilities.can(
+ callCapabilities, PhoneCapabilities.SEPARATE_FROM_CONFERENCE);
+ boolean thisRowCanDisconnect = PhoneCapabilities.can(
+ callCapabilities, PhoneCapabilities.DISCONNECT_FROM_CONFERENCE);
+
+ setCallerInfoForRow(result, contactCache.name, contactCache.number, contactCache.label,
+ contactCache.lookupKey, contactCache.displayPhotoUri, thisRowCanSeparate,
+ thisRowCanDisconnect);
+
+ // Tag the row in the conference participant list with the call id to make it easier to
+ // find calls when contact cache information is loaded.
+ result.setTag(call.getId());
+
+ return result;
+ }
+
+ /**
+ * Replaces the contact info for a participant and triggers a refresh of the UI.
+ *
+ * @param callId The call id.
+ * @param entry The new contact info.
+ */
+ /* package */ void updateContactInfo(String callId, ContactCacheEntry entry) {
+ if (mParticipantsByCallId.containsKey(callId)) {
+ ParticipantInfo participantInfo = mParticipantsByCallId.get(callId);
+ participantInfo.setContactCacheEntry(entry);
+ participantInfo.setCacheLookupComplete(true);
+ refreshView(callId);
+ }
+ }
+
+ /**
+ * Sets the caller information for a row in the conference participant list.
+ *
+ * @param view The view to set the details on.
+ * @param callerName The participant's name.
+ * @param callerNumber The participant's phone number.
+ * @param callerNumberType The participant's phone number typ.e
+ * @param lookupKey The lookup key for the participant (for photo lookup).
+ * @param photoUri The URI of the contact photo.
+ * @param thisRowCanSeparate {@code True} if this participant can separate from the conference.
+ * @param thisRowCanDisconnect {@code True} if this participant can be disconnected.
+ */
+ private final void setCallerInfoForRow(View view, String callerName, String callerNumber,
+ String callerNumberType, String lookupKey, Uri photoUri, boolean thisRowCanSeparate,
+ boolean thisRowCanDisconnect) {
+
+ final ImageView photoView = (ImageView) view.findViewById(R.id.callerPhoto);
+ final TextView nameTextView = (TextView) view.findViewById(R.id.conferenceCallerName);
+ final TextView numberTextView = (TextView) view.findViewById(R.id.conferenceCallerNumber);
+ final TextView numberTypeTextView = (TextView) view.findViewById(
+ R.id.conferenceCallerNumberType);
+ final View endButton = view.findViewById(R.id.conferenceCallerDisconnect);
+ final View separateButton = view.findViewById(R.id.conferenceCallerSeparate);
+
+ endButton.setVisibility(thisRowCanDisconnect ? View.VISIBLE : View.GONE);
+ if (thisRowCanDisconnect) {
+ endButton.setOnClickListener(mDisconnectListener);
+ } else {
+ endButton.setOnClickListener(null);
+ }
+
+ separateButton.setVisibility(thisRowCanSeparate ? View.VISIBLE : View.GONE);
+ if (thisRowCanSeparate) {
+ separateButton.setOnClickListener(mSeparateListener);
+ } else {
+ separateButton.setOnClickListener(null);
+ }
+
+ DefaultImageRequest imageRequest = (photoUri != null) ? null :
+ new DefaultImageRequest(callerName, lookupKey, true /* isCircularPhoto */);
+
+ mContactPhotoManager.loadDirectoryPhoto(photoView, photoUri, false, true, imageRequest);
+
+ // set the caller name
+ nameTextView.setText(callerName);
+
+ // set the caller number in subscript, or make the field disappear.
+ if (TextUtils.isEmpty(callerNumber)) {
+ numberTextView.setVisibility(View.GONE);
+ numberTypeTextView.setVisibility(View.GONE);
+ } else {
+ numberTextView.setVisibility(View.VISIBLE);
+ numberTextView.setText(callerNumber);
+ numberTypeTextView.setVisibility(View.VISIBLE);
+ numberTypeTextView.setText(callerNumberType);
+ }
+ }
+
+ /**
+ * Updates the participant info list which is bound to the ListView. Stores the call and
+ * contact info for all entries. The list is sorted alphabetically by participant name.
+ *
+ * @param conferenceParticipants The calls which make up the conference participants.
+ */
+ private void updateParticipantInfo(List<Call> conferenceParticipants) {
+ final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
+ boolean newParticipantAdded = false;
+ HashSet<String> newCallIds = new HashSet<>(conferenceParticipants.size());
+
+ // Update or add conference participant info.
+ for (Call call : conferenceParticipants) {
+ String callId = call.getId();
+ newCallIds.add(callId);
+ ContactCacheEntry contactCache = cache.getInfo(callId);
+ if (contactCache == null) {
+ contactCache = ContactInfoCache.buildCacheEntryFromCall(mContext, call,
+ call.getState() == Call.State.INCOMING);
+ }
+
+ if (mParticipantsByCallId.containsKey(callId)) {
+ ParticipantInfo participantInfo = mParticipantsByCallId.get(callId);
+ participantInfo.setCall(call);
+ participantInfo.setContactCacheEntry(contactCache);
+ } else {
+ newParticipantAdded = true;
+ ParticipantInfo participantInfo = new ParticipantInfo(call, contactCache);
+ mConferenceParticipants.add(participantInfo);
+ mParticipantsByCallId.put(call.getId(), participantInfo);
+ }
+ }
+
+ // Remove any participants that no longer exist.
+ Iterator<Map.Entry<String, ParticipantInfo>> it =
+ mParticipantsByCallId.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, ParticipantInfo> entry = it.next();
+ String existingCallId = entry.getKey();
+ if (!newCallIds.contains(existingCallId)) {
+ ParticipantInfo existingInfo = entry.getValue();
+ mConferenceParticipants.remove(existingInfo);
+ it.remove();
+ }
+ }
+
+ if (newParticipantAdded) {
+ // Sort the list of participants by contact name.
+ sortParticipantList();
+ }
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Sorts the participant list by contact name.
+ */
+ private void sortParticipantList() {
+ Collections.sort(mConferenceParticipants, new Comparator<ParticipantInfo>() {
+ public int compare(ParticipantInfo p1, ParticipantInfo p2) {
+ // Contact names might be null, so replace with empty string.
+ String p1Name = p1.getContactCacheEntry().name;
+ if (p1Name == null) {
+ p1Name = "";
+ }
+
+ String p2Name = p2.getContactCacheEntry().name;
+ if (p2Name == null) {
+ p2Name = "";
+ }
+
+ return p1Name.compareToIgnoreCase(p2Name);
+ }
+ });
+ }
+}
diff --git a/src/com/android/incallui/InCallPresenter.java b/src/com/android/incallui/InCallPresenter.java
index 7d96025c..2baed50b 100644
--- a/src/com/android/incallui/InCallPresenter.java
+++ b/src/com/android/incallui/InCallPresenter.java
@@ -71,6 +71,8 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
private final List<IncomingCallListener> mIncomingCallListeners = new CopyOnWriteArrayList<>();
private final Set<InCallDetailsListener> mDetailsListeners = Collections.newSetFromMap(
new ConcurrentHashMap<InCallDetailsListener, Boolean>(8, 0.9f, 1));
+ private final Set<CanAddCallListener> mCanAddCallListeners = Collections.newSetFromMap(
+ new ConcurrentHashMap<CanAddCallListener, Boolean>(8, 0.9f, 1));
private final Set<InCallOrientationListener> mOrientationListeners = Collections.newSetFromMap(
new ConcurrentHashMap<InCallOrientationListener, Boolean>(8, 0.9f, 1));
private final Set<InCallEventListener> mInCallEventListeners = Collections.newSetFromMap(
@@ -103,6 +105,12 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
public void onCallRemoved(Phone phone, android.telecom.Call call) {
call.removeListener(mCallListener);
}
+ @Override
+ public void onCanAddCallChanged(Phone phone, boolean canAddCall) {
+ for (CanAddCallListener listener : mCanAddCallListeners) {
+ listener.onCanAddCallChanged(canAddCall);
+ }
+ }
};
private final android.telecom.Call.Listener mCallListener =
@@ -479,6 +487,17 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
}
}
+ public void addCanAddCallListener(CanAddCallListener listener) {
+ Preconditions.checkNotNull(listener);
+ mCanAddCallListeners.add(listener);
+ }
+
+ public void removeCanAddCallListener(CanAddCallListener listener) {
+ if (listener != null) {
+ mCanAddCallListeners.remove(listener);
+ }
+ }
+
public void addOrientationListener(InCallOrientationListener listener) {
Preconditions.checkNotNull(listener);
mOrientationListeners.add(listener);
@@ -1282,6 +1301,10 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
public void onIncomingCall(InCallState oldState, InCallState newState, Call call);
}
+ public interface CanAddCallListener {
+ public void onCanAddCallChanged(boolean canAddCall);
+ }
+
public interface InCallDetailsListener {
public void onDetailsChanged(Call call, android.telecom.Call.Details details);
}
diff --git a/src/com/android/incallui/TelecomAdapter.java b/src/com/android/incallui/TelecomAdapter.java
index 8fa94ea6..a64efcdc 100644
--- a/src/com/android/incallui/TelecomAdapter.java
+++ b/src/com/android/incallui/TelecomAdapter.java
@@ -259,4 +259,9 @@ final class TelecomAdapter implements InCallPhoneListener {
Log.e(this, "error phoneAccountSelected, accountHandle is null");
}
}
+
+ boolean canAddCall() {
+ // Default to true if we are not connected to telecom.
+ return mPhone == null ? true : mPhone.canAddCall();
+ }
}