diff options
Diffstat (limited to 'java/com/android/incallui/ConferenceParticipantListAdapter.java')
-rw-r--r-- | java/com/android/incallui/ConferenceParticipantListAdapter.java | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/java/com/android/incallui/ConferenceParticipantListAdapter.java b/java/com/android/incallui/ConferenceParticipantListAdapter.java new file mode 100644 index 000000000..72c0fcd20 --- /dev/null +++ b/java/com/android/incallui/ConferenceParticipantListAdapter.java @@ -0,0 +1,523 @@ +/* + * 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.support.annotation.Nullable; +import android.support.v4.util.ArrayMap; +import android.text.BidiFormatter; +import android.text.TextDirectionHeuristics; +import android.text.TextUtils; +import android.util.ArraySet; +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.contacts.common.compat.PhoneNumberUtilsCompat; +import com.android.contacts.common.preference.ContactsPreferences; +import com.android.contacts.common.util.ContactDisplayUtils; +import com.android.dialer.common.LogUtil; +import com.android.incallui.ContactInfoCache.ContactCacheEntry; +import com.android.incallui.call.CallList; +import com.android.incallui.call.DialerCall; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +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 { + + /** The ListView containing the participant information. */ + private final ListView mListView; + /** Hashmap to make accessing participant info by call Id faster. */ + private final Map<String, ParticipantInfo> mParticipantsByCallId = new ArrayMap<>(); + /** ContactsPreferences used to lookup displayName preferences */ + @Nullable private final ContactsPreferences mContactsPreferences; + /** Contact photo manager to retrieve cached contact photo information. */ + private final ContactPhotoManager mContactPhotoManager; + /** Listener used to handle tap of the "disconnect' button for a participant. */ + private View.OnClickListener mDisconnectListener = + new View.OnClickListener() { + @Override + public void onClick(View view) { + DialerCall call = getCallFromView(view); + LogUtil.i( + "ConferenceParticipantListAdapter.mDisconnectListener.onClick", "call: " + call); + if (call != null) { + call.disconnect(); + } + } + }; + /** Listener used to handle tap of the "separate' button for a participant. */ + private View.OnClickListener mSeparateListener = + new View.OnClickListener() { + @Override + public void onClick(View view) { + DialerCall call = getCallFromView(view); + LogUtil.i("ConferenceParticipantListAdapter.mSeparateListener.onClick", "call: " + call); + if (call != null) { + call.splitFromConference(); + } + } + }; + /** The conference participants to show in the ListView. */ + private List<ParticipantInfo> mConferenceParticipants = new ArrayList<>(); + /** {@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 contactPhotoManager The contact photo manager, used to load contact photos. + */ + public ConferenceParticipantListAdapter( + ListView listView, ContactPhotoManager contactPhotoManager) { + + mListView = listView; + mContactsPreferences = ContactsPreferencesFactory.newContactsPreferences(getContext()); + 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<DialerCall> conferenceParticipants, boolean parentCanSeparate) { + if (mContactsPreferences != null) { + mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY); + mContactsPreferences.refreshValue(ContactsPreferences.SORT_ORDER_KEY); + } + 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(DialerCall call) { + String callId = call.getId(); + + if (mParticipantsByCallId.containsKey(callId)) { + ParticipantInfo participantInfo = mParticipantsByCallId.get(callId); + participantInfo.setCall(call); + refreshView(callId); + } + } + + private Context getContext() { + return mListView.getContext(); + } + + /** + * 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 + ? LayoutInflater.from(parent.getContext()) + .inflate(R.layout.caller_in_conference, parent, false) + : convertView; + + ParticipantInfo participantInfo = mConferenceParticipants.get(position); + DialerCall call = participantInfo.getCall(); + ContactCacheEntry contactCache = participantInfo.getContactCacheEntry(); + + final ContactInfoCache cache = ContactInfoCache.getInstance(getContext()); + + // 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() == DialerCall.State.INCOMING, + new ContactLookupCallback(this)); + } + + boolean thisRowCanSeparate = + mParentCanSeparate + && call.can(android.telecom.Call.Details.CAPABILITY_SEPARATE_FROM_CONFERENCE); + boolean thisRowCanDisconnect = + call.can(android.telecom.Call.Details.CAPABILITY_DISCONNECT_FROM_CONFERENCE); + + setCallerInfoForRow( + result, + contactCache.namePrimary, + ContactDisplayUtils.getPreferredDisplayName( + contactCache.namePrimary, contactCache.nameAlternative, mContactsPreferences), + 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 void setCallerInfoForRow( + View view, + String callerName, + String preferredName, + 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(preferredName); + + // 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( + PhoneNumberUtilsCompat.createTtsSpannable( + BidiFormatter.getInstance().unicodeWrap(callerNumber, TextDirectionHeuristics.LTR))); + 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<DialerCall> conferenceParticipants) { + final ContactInfoCache cache = ContactInfoCache.getInstance(getContext()); + boolean newParticipantAdded = false; + Set<String> newCallIds = new ArraySet<>(conferenceParticipants.size()); + + // Update or add conference participant info. + for (DialerCall call : conferenceParticipants) { + String callId = call.getId(); + newCallIds.add(callId); + ContactCacheEntry contactCache = cache.getInfo(callId); + if (contactCache == null) { + contactCache = + ContactInfoCache.buildCacheEntryFromCall( + getContext(), call, call.getState() == DialerCall.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>() { + @Override + public int compare(ParticipantInfo p1, ParticipantInfo p2) { + // Contact names might be null, so replace with empty string. + ContactCacheEntry c1 = p1.getContactCacheEntry(); + String p1Name = + ContactDisplayUtils.getPreferredSortName( + c1.namePrimary, c1.nameAlternative, mContactsPreferences); + p1Name = p1Name != null ? p1Name : ""; + + ContactCacheEntry c2 = p2.getContactCacheEntry(); + String p2Name = + ContactDisplayUtils.getPreferredSortName( + c2.namePrimary, c2.nameAlternative, mContactsPreferences); + p2Name = p2Name != null ? p2Name : ""; + + return p1Name.compareToIgnoreCase(p2Name); + } + }); + } + + private DialerCall getCallFromView(View view) { + View parent = (View) view.getParent(); + String callId = (String) parent.getTag(); + return CallList.getInstance().getCallById(callId); + } + + /** + * 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<>(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); + } + } + } + + /** + * Internal class which represents a participant. Includes a reference to the {@link DialerCall} + * and the corresponding {@link ContactCacheEntry} for the participant. + */ + private static class ParticipantInfo { + + private DialerCall mCall; + private ContactCacheEntry mContactCacheEntry; + private boolean mCacheLookupComplete = false; + + public ParticipantInfo(DialerCall call, ContactCacheEntry contactCacheEntry) { + mCall = call; + mContactCacheEntry = contactCacheEntry; + } + + public DialerCall getCall() { + return mCall; + } + + public void setCall(DialerCall 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(); + } + } +} |