diff options
author | Tyler Gunn <tgunn@google.com> | 2014-11-10 11:56:43 -0800 |
---|---|---|
committer | Tyler Gunn <tgunn@google.com> | 2014-11-10 11:56:43 -0800 |
commit | 0cff18feff43a6915919ac8e32fa44502aa6c320 (patch) | |
tree | fdfea404dd180cd11f6d699a2c125a08a5b4ef01 | |
parent | 5afa4c6b44d4857fc5be60784ffce116e80291b4 (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
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); + } + }); + } +} |