diff options
author | Tyler Gunn <tgunn@google.com> | 2014-11-04 14:59:14 -0800 |
---|---|---|
committer | Steve Kondik <steve@cyngn.com> | 2014-12-28 00:47:02 -0800 |
commit | 69dd0f6e102b777b86f2c1213ac5629c15352ddd (patch) | |
tree | f5d32e47f9d3e66a5704565bb4a11e3b13db65e3 | |
parent | 013959488e93d3657bc4ffd3f59e39ba71ea3130 (diff) | |
download | packages_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.xml | 53 | ||||
-rw-r--r-- | src/com/android/incallui/Call.java | 36 | ||||
-rw-r--r-- | src/com/android/incallui/CallButtonPresenter.java | 29 | ||||
-rw-r--r-- | src/com/android/incallui/CallCardFragment.java | 44 | ||||
-rw-r--r-- | src/com/android/incallui/CallCardPresenter.java | 190 | ||||
-rw-r--r-- | src/com/android/incallui/CallList.java | 1 | ||||
-rw-r--r-- | src/com/android/incallui/ConferenceManagerFragment.java | 110 | ||||
-rw-r--r-- | src/com/android/incallui/ConferenceManagerPresenter.java | 132 | ||||
-rw-r--r-- | src/com/android/incallui/ConferenceParticipantListAdapter.java | 503 | ||||
-rw-r--r-- | src/com/android/incallui/InCallPresenter.java | 23 | ||||
-rw-r--r-- | src/com/android/incallui/TelecomAdapter.java | 5 |
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(); + } } |