summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyler Gunn <tgunn@google.com>2014-11-10 11:56:43 -0800
committerTyler Gunn <tgunn@google.com>2014-11-10 11:56:43 -0800
commit0cff18feff43a6915919ac8e32fa44502aa6c320 (patch)
treefdfea404dd180cd11f6d699a2c125a08a5b4ef01
parent5afa4c6b44d4857fc5be60784ffce116e80291b4 (diff)
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
-rw-r--r--InCallUI/res/layout/conference_manager_fragment.xml52
-rw-r--r--InCallUI/src/com/android/incallui/CallCardFragment.java10
-rw-r--r--InCallUI/src/com/android/incallui/CallCardPresenter.java24
-rw-r--r--InCallUI/src/com/android/incallui/ConferenceManagerFragment.java101
-rw-r--r--InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java118
-rw-r--r--InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java503
6 files changed, 573 insertions, 235 deletions
diff --git a/InCallUI/res/layout/conference_manager_fragment.xml b/InCallUI/res/layout/conference_manager_fragment.xml
index c6c1af9de..9aa172ea7 100644
--- a/InCallUI/res/layout/conference_manager_fragment.xml
+++ b/InCallUI/res/layout/conference_manager_fragment.xml
@@ -25,50 +25,12 @@
android:paddingTop="@dimen/conference_call_manager_padding_top"
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/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java
index 8b37c608a..8aa8d2349 100644
--- a/InCallUI/src/com/android/incallui/CallCardFragment.java
+++ b/InCallUI/src/com/android/incallui/CallCardFragment.java
@@ -789,6 +789,16 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPr
}
/**
+ * 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;
+ }
+
+ /**
* Get the overall InCallUI background colors and apply to call card.
*/
public void updateColors() {
diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java
index a757f09ab..e6ed7fffd 100644
--- a/InCallUI/src/com/android/incallui/CallCardPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java
@@ -24,7 +24,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;
@@ -196,7 +195,11 @@ 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);
@@ -305,13 +308,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() {
@@ -673,6 +684,7 @@ public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
void setPhotoVisible(boolean isVisible);
void setProgressSpinnerVisible(boolean visible);
void showManageConferenceCallButton(boolean visible);
+ boolean isManageConferenceVisible();
}
private TelecomManager getTelecomManager() {
diff --git a/InCallUI/src/com/android/incallui/ConferenceManagerFragment.java b/InCallUI/src/com/android/incallui/ConferenceManagerFragment.java
index c6ae19a1c..c25a1ab4b 100644
--- a/InCallUI/src/com/android/incallui/ConferenceManagerFragment.java
+++ b/InCallUI/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() {
@@ -64,45 +64,16 @@ public class ConferenceManagerFragment
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]);
- initializeRow(mConferenceCallList[i], i);
- }
-
+ 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;
}
- /**
- * Setup listeners for disconnecting and separating child calls.
- */
- private void initializeRow(View rowView, final int rowId) {
- View endButton = rowView.findViewById(R.id.conferenceCallerDisconnect);
- endButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- getPresenter().endConferenceConnection(rowId);
- }
- });
-
- View separateButton = rowView.findViewById(R.id.conferenceCallerSeparate);
- separateButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- getPresenter().separateConferenceConnection(rowId);
- }
- });
- }
-
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
@@ -135,56 +106,18 @@ public class ConferenceManagerFragment
}
@Override
- public void setRowVisible(int rowId, boolean on) {
- mConferenceCallList[rowId].setVisibility(on ? View.VISIBLE : View.GONE);
- }
+ public void update(Context context, List<Call> participants, boolean parentCanSeparate) {
+ if (mConferenceParticipantListAdapter == null) {
+ mConferenceParticipantListAdapter = new ConferenceParticipantListAdapter(
+ mConferenceParticipantList, context, mInflater, mContactPhotoManager);
- /**
- * 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, 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);
+ mConferenceParticipantList.setAdapter(mConferenceParticipantListAdapter);
}
+ mConferenceParticipantListAdapter.updateParticipants(participants, parentCanSeparate);
}
@Override
- public void updateEndButtonForRow(int rowId, boolean canDisconnect) {
- View endButton = mConferenceCallList[rowId].findViewById(R.id.conferenceCallerDisconnect);
- endButton.setVisibility(canDisconnect ? View.VISIBLE : View.GONE);
- }
-
- @Override
- public void updateSeparateButtonForRow(int rowId, boolean canSeparate) {
- View separateButton =
- mConferenceCallList[rowId].findViewById(R.id.conferenceCallerSeparate);
- separateButton.setVisibility(canSeparate ? View.VISIBLE : View.GONE);
+ public void refreshCall(Call call) {
+ mConferenceParticipantListAdapter.refreshCall(call);
}
}
diff --git a/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java b/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java
index e227c1880..40c2dcdaf 100644
--- a/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java
+++ b/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java
@@ -28,6 +28,9 @@ import com.android.incallui.InCallPresenter.InCallStateListener;
import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Logic for call buttons.
*/
@@ -35,9 +38,6 @@ public class ConferenceManagerPresenter
extends Presenter<ConferenceManagerPresenter.ConferenceManagerUi>
implements InCallStateListener, InCallDetailsListener {
- private static final int MAX_CALLERS_IN_CONFERENCE = 5;
-
- private String[] mCallerIds = new String[0];
private Context mContext;
@Override
@@ -83,13 +83,7 @@ public class ConferenceManagerPresenter
if (call.can(PhoneCapabilities.DISCONNECT_FROM_CONFERENCE) != canDisconnect
|| call.can(PhoneCapabilities.SEPARATE_FROM_CONFERENCE) != canSeparate) {
- for (int i = 0; i < mCallerIds.length; i++) {
- if (mCallerIds[i] == call.getId()) {
- getUi().updateSeparateButtonForRow(i, canSeparate);
- getUi().updateEndButtonForRow(i, canDisconnect);
- break;
- }
- }
+ getUi().refreshCall(call);
}
if (!PhoneCapabilities.can(
@@ -104,6 +98,11 @@ public class ConferenceManagerPresenter
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();
@@ -111,9 +110,12 @@ public class ConferenceManagerPresenter
return;
}
- // getChildCallIds() always returns a valid Set
- mCallerIds = currentCall.getChildCallIds().toArray(new String[0]);
- Log.d(this, "Number of calls is " + String.valueOf(mCallerIds.length));
+ 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(calls.size()));
// Users can split out a call from the conference call if either the active call or the
// holding call is empty. If both are filled, users can not split out another call.
@@ -121,97 +123,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) {
- Call call = callList.getCallById(currentCall.getChildCallIds().get(i));
- int callCapabilities = call.getTelecommCall().getDetails().getCallCapabilities();
- boolean thisRowCanSeparate = canSeparate && PhoneCapabilities.can(
- callCapabilities, PhoneCapabilities.SEPARATE_FROM_CONFERENCE);
- boolean thisRowCanDisconnect = PhoneCapabilities.can(
- callCapabilities, PhoneCapabilities.DISCONNECT_FROM_CONFERENCE);
- // Fill in the row in the UI for this caller.
- ContactCacheEntry contactCache =
- ContactInfoCache.getInstance(mContext).getInfo(mCallerIds[i]);
- if (contactCache == null) {
- contactCache = ContactInfoCache.buildCacheEntryFromCall(mContext, call,
- call.getState() == Call.State.INCOMING);
- }
-
- updateManageConferenceRow(
- i /* row index */,
- 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().updateSeparateButtonForRow(i, canSeparate);
- getUi().updateEndButtonForRow(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 updateSeparateButtonForRow(int rowId, boolean canSeparate);
- void updateEndButtonForRow(int rowId, boolean canDisconnect);
+ void update(Context context, List<Call> participants, boolean parentCanSeparate);
+ void refreshCall(Call call);
}
}
diff --git a/InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java b/InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java
new file mode 100644
index 000000000..641261e0f
--- /dev/null
+++ b/InCallUI/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);
+ }
+ });
+ }
+}