Merge "Separate work and personal contacts search result am: 9045160771 am: 0ecb6b9a9b" into nyc-mr1-dev-plus-aosp

This commit is contained in:
Android Build Merger (Role)
2016-05-16 15:47:46 +00:00
committed by Android (Google) Code Review
5 changed files with 240 additions and 63 deletions
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2009 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 A`NY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Layout used for list section separators. -->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="@string/work_directory_display_name"
style="@style/DirectoryHeaderStyle"
/>
+6
View File
@@ -188,4 +188,10 @@
<dimen name="fastscroll_preview_margin_left_right">8dp</dimen> <dimen name="fastscroll_preview_margin_left_right">8dp</dimen>
<dimen name="fastscroll_preview_text_size">24sp</dimen> <dimen name="fastscroll_preview_text_size">24sp</dimen>
<dimen name="directory_header_padding_start">16dp</dimen>
<dimen name="directory_header_padding_end">32dp</dimen>
<dimen name="directory_header_padding_top">18dp</dimen>
<dimen name="directory_header_padding_bottom">8dp</dimen>
<dimen name="directory_header_text_size">14sp</dimen>
</resources> </resources>
+2
View File
@@ -973,4 +973,6 @@
<!-- The accessibility text read when the sim chooser pops up to read the current selected sim --> <!-- The accessibility text read when the sim chooser pops up to read the current selected sim -->
<string name="selected_sim_content_message"><xliff:g id="selected_sim">%s</xliff:g> selected</string> <string name="selected_sim_content_message"><xliff:g id="selected_sim">%s</xliff:g> selected</string>
<string name="work_directory_display_name">Work Profile contacts</string>
</resources> </resources>
+10
View File
@@ -623,4 +623,14 @@
<item name="android:textColor">@android:color/white</item> <item name="android:textColor">@android:color/white</item>
<item name="android:gravity">center</item> <item name="android:gravity">center</item>
</style> </style>
<style name="DirectoryHeaderStyle">
<item name="android:paddingLeft">@dimen/directory_header_padding_start</item>
<item name="android:paddingStart">@dimen/directory_header_padding_start</item>
<item name="android:paddingRight">@dimen/directory_header_padding_end</item>
<item name="android:paddingEnd">@dimen/directory_header_padding_end</item>
<item name="android:paddingTop">@dimen/directory_header_padding_top</item>
<item name="android:paddingBottom">@dimen/directory_header_padding_bottom</item>
<item name="android:textSize">@dimen/directory_header_text_size</item>
<item name="android:textStyle">bold</item>
</style>
</resources> </resources>
@@ -22,12 +22,17 @@ import android.support.v4.util.Pair;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.util.Rfc822Token; import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer; import android.text.util.Rfc822Tokenizer;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter; import android.widget.Filter;
import android.widget.TextView;
import com.android.ex.chips.BaseRecipientAdapter; import com.android.ex.chips.BaseRecipientAdapter;
import com.android.ex.chips.RecipientAlternatesAdapter; import com.android.ex.chips.RecipientAlternatesAdapter;
import com.android.ex.chips.RecipientAlternatesAdapter.RecipientMatchCallback; import com.android.ex.chips.RecipientAlternatesAdapter.RecipientMatchCallback;
import com.android.ex.chips.RecipientEntry; import com.android.ex.chips.RecipientEntry;
import com.android.messaging.R;
import com.android.messaging.util.Assert; import com.android.messaging.util.Assert;
import com.android.messaging.util.Assert.DoesNotRunOnMainThread; import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
import com.android.messaging.util.BugleGservices; import com.android.messaging.util.BugleGservices;
@@ -54,6 +59,18 @@ import java.util.Map;
* for {@link ContactRecipientAutoCompleteView} * for {@link ContactRecipientAutoCompleteView}
*/ */
public final class ContactRecipientAdapter extends BaseRecipientAdapter { public final class ContactRecipientAdapter extends BaseRecipientAdapter {
private static final int WORD_DIRECTORY_HEADER_POS_NONE = -1;
/**
* Stores the index of work directory header.
*/
private int mWorkDirectoryHeaderPos = WORD_DIRECTORY_HEADER_POS_NONE;
private final LayoutInflater mInflater;
/**
* Type of directory entry.
*/
private static final int ENTRY_TYPE_DIRECTORY = RecipientEntry.ENTRY_TYPE_SIZE;
public ContactRecipientAdapter(final Context context, public ContactRecipientAdapter(final Context context,
final ContactListItemView.HostInterface clivHost) { final ContactListItemView.HostInterface clivHost) {
this(context, Integer.MAX_VALUE, QUERY_TYPE_PHONE, clivHost); this(context, Integer.MAX_VALUE, QUERY_TYPE_PHONE, clivHost);
@@ -63,6 +80,7 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter {
final int queryMode, final ContactListItemView.HostInterface clivHost) { final int queryMode, final ContactListItemView.HostInterface clivHost) {
super(context, preferredMaxResultCount, queryMode); super(context, preferredMaxResultCount, queryMode);
setPhotoManager(new ContactRecipientPhotoManager(context, clivHost)); setPhotoManager(new ContactRecipientPhotoManager(context, clivHost));
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
} }
@Override @Override
@@ -82,6 +100,7 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter {
* results. * results.
*/ */
public class ContactFilter extends Filter { public class ContactFilter extends Filter {
// Used to sort filtered contacts when it has combined results from email and phone. // Used to sort filtered contacts when it has combined results from email and phone.
private final RecipientEntryComparator mComparator = new RecipientEntryComparator(); private final RecipientEntryComparator mComparator = new RecipientEntryComparator();
@@ -96,8 +115,7 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter {
* return the merged results. * return the merged results.
*/ */
@DoesNotRunOnMainThread @DoesNotRunOnMainThread
private Pair<Cursor, Boolean> getFilteredResultsCursor(final Context context, private CursorResult getFilteredResultsCursor(final String searchText) {
final String searchText) {
Assert.isNotMainThread(); Assert.isNotMainThread();
if (BugleGservices.get().getBoolean( if (BugleGservices.get().getBoolean(
BugleGservicesKeys.ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS, BugleGservicesKeys.ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS,
@@ -107,44 +125,35 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter {
.filterPhones(getContext(), searchText).performSynchronousQuery(); .filterPhones(getContext(), searchText).performSynchronousQuery();
final Cursor personalFilterEmailsCursor = ContactUtil final Cursor personalFilterEmailsCursor = ContactUtil
.filterEmails(getContext(), searchText).performSynchronousQuery(); .filterEmails(getContext(), searchText).performSynchronousQuery();
Cursor resultCursor; final Cursor personalCursor = new MergeCursor(
new Cursor[]{personalFilterEmailsCursor, personalFilterPhonesCursor});
final CursorResult cursorResult =
new CursorResult(personalCursor, false /* sorted */);
if (OsUtil.isAtLeastN()) { if (OsUtil.isAtLeastN()) {
// Including enterprise result starting from N. // Including enterprise result starting from N.
final Cursor enterpriseFilterPhonesCursor = ContactUtil.filterPhonesEnterprise( final Cursor enterpriseFilterPhonesCursor = ContactUtil.filterPhonesEnterprise(
getContext(), searchText).performSynchronousQuery(); getContext(), searchText).performSynchronousQuery();
final Cursor enterpriseFilterEmailsCursor = ContactUtil.filterEmailsEnterprise( final Cursor enterpriseFilterEmailsCursor = ContactUtil.filterEmailsEnterprise(
getContext(), searchText).performSynchronousQuery(); getContext(), searchText).performSynchronousQuery();
// TODO: Separating enterprise result from personal result (b/26021888) final Cursor enterpriseCursor = new MergeCursor(
resultCursor = new MergeCursor( new Cursor[]{enterpriseFilterEmailsCursor,
new Cursor[]{personalFilterEmailsCursor, enterpriseFilterEmailsCursor, enterpriseFilterPhonesCursor});
personalFilterPhonesCursor, enterpriseFilterPhonesCursor}); cursorResult.enterpriseCursor = enterpriseCursor;
} else {
resultCursor = new MergeCursor(
new Cursor[]{personalFilterEmailsCursor, personalFilterPhonesCursor});
} }
return Pair.create( return cursorResult;
resultCursor,
false /* the merged cursor is not sorted */
);
} else { } else {
final Cursor personalFilterDestinationCursor = ContactUtil final Cursor personalFilterDestinationCursor = ContactUtil
.filterDestination(getContext(), searchText).performSynchronousQuery(); .filterDestination(getContext(), searchText).performSynchronousQuery();
Cursor resultCursor; final CursorResult cursorResult = new CursorResult(personalFilterDestinationCursor,
boolean sorted; true);
if (OsUtil.isAtLeastN()) { if (OsUtil.isAtLeastN()) {
// Including enterprise result starting from N. // Including enterprise result starting from N.
final Cursor enterpriseFilterDestinationCursor = ContactUtil final Cursor enterpriseFilterDestinationCursor = ContactUtil
.filterDestinationEnterprise(getContext(), searchText) .filterDestinationEnterprise(getContext(), searchText)
.performSynchronousQuery(); .performSynchronousQuery();
// TODO: Separating enterprise result from personal result (b/26021888) cursorResult.enterpriseCursor = enterpriseFilterDestinationCursor;
resultCursor = new MergeCursor(new Cursor[]{personalFilterDestinationCursor,
enterpriseFilterDestinationCursor});
sorted = false;
} else {
resultCursor = personalFilterDestinationCursor;
sorted = true;
} }
return Pair.create(resultCursor, sorted); return cursorResult;
} }
} }
@@ -163,44 +172,57 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter {
// Query for auto-complete results, since performFiltering() is not done on the // Query for auto-complete results, since performFiltering() is not done on the
// main thread, perform the cursor loader queries directly. // main thread, perform the cursor loader queries directly.
final Pair<Cursor, Boolean> filteredResults = getFilteredResultsCursor(getContext(),
searchText);
final Cursor cursor = filteredResults.first;
final boolean sorted = filteredResults.second;
if (cursor != null) {
try {
final List<RecipientEntry> entries = new ArrayList<RecipientEntry>();
// First check if the constraint is a valid SMS destination. If so, add the final CursorResult cursorResult = getFilteredResultsCursor(searchText);
// destination as a suggestion item to the drop down. final List<RecipientEntry> entries = new ArrayList<>();
if (PhoneUtils.isValidSmsMmsDestination(searchText)) {
entries.add(ContactRecipientEntryUtils
.constructSendToDestinationEntry(searchText));
}
HashSet<Long> existingContactIds = new HashSet<Long>(); // First check if the constraint is a valid SMS destination. If so, add the
while (cursor.moveToNext()) { // destination as a suggestion item to the drop down.
// Make sure there's only one first-level contact (i.e. contact for which if (PhoneUtils.isValidSmsMmsDestination(searchText)) {
// we show the avatar picture and name) for every contact id. entries.add(ContactRecipientEntryUtils
final long contactId = cursor.getLong(ContactUtil.INDEX_CONTACT_ID); .constructSendToDestinationEntry(searchText));
final boolean isFirstLevel = !existingContactIds.contains(contactId); }
if (isFirstLevel) {
existingContactIds.add(contactId);
}
entries.add(ContactUtil.createRecipientEntryForPhoneQuery(cursor,
isFirstLevel));
}
if (!sorted) { // Only show work directory header if more than one result in work directory.
Collections.sort(entries, mComparator); int workDirectoryHeaderPos = WORD_DIRECTORY_HEADER_POS_NONE;
} if (cursorResult.enterpriseCursor != null
results.values = entries; && cursorResult.enterpriseCursor.getCount() > 0) {
results.count = 1; if (cursorResult.personalCursor != null) {
workDirectoryHeaderPos = entries.size();
} finally { workDirectoryHeaderPos += cursorResult.personalCursor.getCount();
cursor.close();
} }
} }
final Cursor[] cursors = new Cursor[]{cursorResult.personalCursor,
cursorResult.enterpriseCursor};
for (Cursor cursor : cursors) {
if (cursor != null) {
try {
final List<RecipientEntry> tempEntries = new ArrayList<>();
HashSet<Long> existingContactIds = new HashSet<>();
while (cursor.moveToNext()) {
// Make sure there's only one first-level contact (i.e. contact for
// which we show the avatar picture and name) for every contact id.
final long contactId = cursor.getLong(ContactUtil.INDEX_CONTACT_ID);
final boolean isFirstLevel = !existingContactIds.contains(contactId);
if (isFirstLevel) {
existingContactIds.add(contactId);
}
tempEntries.add(ContactUtil.createRecipientEntryForPhoneQuery(cursor,
isFirstLevel));
}
if (!cursorResult.isSorted) {
Collections.sort(tempEntries, mComparator);
}
entries.addAll(tempEntries);
} finally {
cursor.close();
}
}
}
results.values = new ContactReceipientFilterResult(entries, workDirectoryHeaderPos);
results.count = 1;
return results; return results;
} }
@@ -209,16 +231,20 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter {
mCurrentConstraint = constraint; mCurrentConstraint = constraint;
clearTempEntries(); clearTempEntries();
if (results.values != null) { final ContactReceipientFilterResult contactReceipientFilterResult
@SuppressWarnings("unchecked") = (ContactReceipientFilterResult) results.values;
final List<RecipientEntry> entries = (List<RecipientEntry>) results.values; if (contactReceipientFilterResult != null) {
updateEntries(entries); mWorkDirectoryHeaderPos = contactReceipientFilterResult.workDirectoryPos;
} else { if (contactReceipientFilterResult.recipientEntries != null) {
updateEntries(Collections.<RecipientEntry>emptyList()); updateEntries(contactReceipientFilterResult.recipientEntries);
} else {
updateEntries(Collections.<RecipientEntry>emptyList());
}
} }
} }
private class RecipientEntryComparator implements Comparator<RecipientEntry> { private class RecipientEntryComparator implements Comparator<RecipientEntry> {
private final Collator mCollator; private final Collator mCollator;
public RecipientEntryComparator() { public RecipientEntryComparator() {
@@ -272,6 +298,38 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter {
} }
} }
} }
private class CursorResult {
public final Cursor personalCursor;
public Cursor enterpriseCursor;
public final boolean isSorted;
public CursorResult(Cursor personalCursor, boolean isSorted) {
this.personalCursor = personalCursor;
this.isSorted = isSorted;
}
}
private class ContactReceipientFilterResult {
/**
* Recipient entries in all directories.
*/
public final List<RecipientEntry> recipientEntries;
/**
* Index of row that showing work directory header.
*/
public final int workDirectoryPos;
public ContactReceipientFilterResult(List<RecipientEntry> recipientEntries,
int workDirectoryPos) {
this.recipientEntries = recipientEntries;
this.workDirectoryPos = workDirectoryPos;
}
}
} }
/** /**
@@ -318,4 +376,81 @@ public final class ContactRecipientAdapter extends BaseRecipientAdapter {
// report matches // report matches
callback.matchesFound(recipientEntries); callback.matchesFound(recipientEntries);
} }
/**
* We handle directory header here and then delegate the work of creating recipient views to
* the {@link BaseRecipientAdapter}. Please notice that we need to fix the position
* before passing to {@link BaseRecipientAdapter} because it is not aware of the existence of
* directory headers.
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView;
if (isDirectoryEntry(position)) {
if (convertView == null) {
textView = (TextView) mInflater.inflate(R.layout.work_directory_header, parent,
false);
} else {
textView = (TextView) convertView;
}
return textView;
}
return super.getView(fixPosition(position), convertView, parent);
}
@Override
public RecipientEntry getItem(int position) {
if (isDirectoryEntry(position)) {
return null;
}
return super.getItem(fixPosition(position));
}
@Override
public int getViewTypeCount() {
return RecipientEntry.ENTRY_TYPE_SIZE + 1;
}
@Override
public int getItemViewType(int position) {
if (isDirectoryEntry(position)) {
return ENTRY_TYPE_DIRECTORY;
}
return super.getItemViewType(fixPosition(position));
}
@Override
public boolean isEnabled(int position) {
if (isDirectoryEntry(position)) {
return false;
}
return super.isEnabled(fixPosition(position));
}
@Override
public int getCount() {
return super.getCount() + ((hasWorkDirectoryHeader()) ? 1 : 0);
}
private boolean isDirectoryEntry(int position) {
return position == mWorkDirectoryHeaderPos;
}
/**
* @return the position of items without counting directory headers.
*/
private int fixPosition(int position) {
if (hasWorkDirectoryHeader()) {
Assert.isTrue(position != mWorkDirectoryHeaderPos);
if (position > mWorkDirectoryHeaderPos) {
return position - 1;
}
}
return position;
}
private boolean hasWorkDirectoryHeader() {
return mWorkDirectoryHeaderPos != WORD_DIRECTORY_HEADER_POS_NONE;
}
} }