diff options
Diffstat (limited to 'src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java')
-rw-r--r-- | src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java b/src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java new file mode 100644 index 0000000..62483a0 --- /dev/null +++ b/src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2015 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.messaging.datamodel; + +import android.database.Cursor; +import android.database.MatrixCursor; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.support.v4.util.SimpleArrayMap; + +import com.android.messaging.util.Assert; +import com.android.messaging.util.ContactUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +/** + * A cursor builder that takes the frequent contacts cursor and aggregate it with the all contacts + * cursor to fill in contact details such as phone numbers and strip away invalid contacts. + * + * Because the frequent contact list depends on the loading of two cursors, it needs to temporarily + * store the cursor that it receives with setFrequents() and setAllContacts() calls. Because it + * doesn't know which one will be finished first, it always checks whether both cursors are ready + * to pull data from and construct the aggregate cursor when it's ready to do so. Note that + * this cursor builder doesn't assume ownership of the cursors passed in - it merely references + * them and always does a isClosed() check before consuming them. The ownership still belongs to + * the loader framework and the cursor may be closed when the UI is torn down. + */ +public class FrequentContactsCursorBuilder { + private Cursor mAllContactsCursor; + private Cursor mFrequentContactsCursor; + + /** + * Sets the frequent contacts cursor as soon as it is loaded, or null if it's reset. + * @return this builder instance for chained operations + */ + public FrequentContactsCursorBuilder setFrequents(final Cursor frequentContactsCursor) { + mFrequentContactsCursor = frequentContactsCursor; + return this; + } + + /** + * Sets the all contacts cursor as soon as it is loaded, or null if it's reset. + * @return this builder instance for chained operations + */ + public FrequentContactsCursorBuilder setAllContacts(final Cursor allContactsCursor) { + mAllContactsCursor = allContactsCursor; + return this; + } + + /** + * Reset this builder. Must be called when the consumer resets its data. + */ + public void resetBuilder() { + mAllContactsCursor = null; + mFrequentContactsCursor = null; + } + + /** + * Attempt to build the cursor records from the frequent and all contacts cursor if they + * are both ready to be consumed. + * @return the frequent contact cursor if built successfully, or null if it can't be built yet. + */ + public Cursor build() { + if (mFrequentContactsCursor != null && mAllContactsCursor != null) { + Assert.isTrue(!mFrequentContactsCursor.isClosed()); + Assert.isTrue(!mAllContactsCursor.isClosed()); + + // Frequent contacts cursor has one record per contact, plus it doesn't contain info + // such as phone number and type. In order for the records to be usable by Bugle, we + // would like to populate it with information from the all contacts cursor. + final MatrixCursor retCursor = new MatrixCursor(ContactUtil.PhoneQuery.PROJECTION); + + // First, go through the frequents cursor and take note of all lookup keys and their + // corresponding rank in the frequents list. + final SimpleArrayMap<String, Integer> lookupKeyToRankMap = + new SimpleArrayMap<String, Integer>(); + int oldPosition = mFrequentContactsCursor.getPosition(); + int rank = 0; + mFrequentContactsCursor.moveToPosition(-1); + while (mFrequentContactsCursor.moveToNext()) { + final String lookupKey = mFrequentContactsCursor.getString( + ContactUtil.INDEX_LOOKUP_KEY_FREQUENT); + lookupKeyToRankMap.put(lookupKey, rank++); + } + mFrequentContactsCursor.moveToPosition(oldPosition); + + // Second, go through the all contacts cursor once and retrieve all information + // (multiple phone numbers etc.) and store that in an array list. Since the all + // contacts list only contains phone contacts, this step will ensure that we filter + // out any invalid/email contacts in the frequents list. + final ArrayList<Object[]> rows = + new ArrayList<Object[]>(mFrequentContactsCursor.getCount()); + oldPosition = mAllContactsCursor.getPosition(); + mAllContactsCursor.moveToPosition(-1); + while (mAllContactsCursor.moveToNext()) { + final String lookupKey = mAllContactsCursor.getString(ContactUtil.INDEX_LOOKUP_KEY); + if (lookupKeyToRankMap.containsKey(lookupKey)) { + final Object[] row = new Object[ContactUtil.PhoneQuery.PROJECTION.length]; + row[ContactUtil.INDEX_DATA_ID] = + mAllContactsCursor.getLong(ContactUtil.INDEX_DATA_ID); + row[ContactUtil.INDEX_CONTACT_ID] = + mAllContactsCursor.getLong(ContactUtil.INDEX_CONTACT_ID); + row[ContactUtil.INDEX_LOOKUP_KEY] = + mAllContactsCursor.getString(ContactUtil.INDEX_LOOKUP_KEY); + row[ContactUtil.INDEX_DISPLAY_NAME] = + mAllContactsCursor.getString(ContactUtil.INDEX_DISPLAY_NAME); + row[ContactUtil.INDEX_PHOTO_URI] = + mAllContactsCursor.getString(ContactUtil.INDEX_PHOTO_URI); + row[ContactUtil.INDEX_PHONE_EMAIL] = + mAllContactsCursor.getString(ContactUtil.INDEX_PHONE_EMAIL); + row[ContactUtil.INDEX_PHONE_EMAIL_TYPE] = + mAllContactsCursor.getInt(ContactUtil.INDEX_PHONE_EMAIL_TYPE); + row[ContactUtil.INDEX_PHONE_EMAIL_LABEL] = + mAllContactsCursor.getString(ContactUtil.INDEX_PHONE_EMAIL_LABEL); + rows.add(row); + } + } + mAllContactsCursor.moveToPosition(oldPosition); + + // Now we have a list of rows containing frequent contacts in alphabetical order. + // Therefore, sort all the rows according to their actual ranks in the frequents list. + Collections.sort(rows, new Comparator<Object[]>() { + @Override + public int compare(final Object[] lhs, final Object[] rhs) { + final String lookupKeyLhs = (String) lhs[ContactUtil.INDEX_LOOKUP_KEY]; + final String lookupKeyRhs = (String) rhs[ContactUtil.INDEX_LOOKUP_KEY]; + Assert.isTrue(lookupKeyToRankMap.containsKey(lookupKeyLhs) && + lookupKeyToRankMap.containsKey(lookupKeyRhs)); + final int rankLhs = lookupKeyToRankMap.get(lookupKeyLhs); + final int rankRhs = lookupKeyToRankMap.get(lookupKeyRhs); + if (rankLhs < rankRhs) { + return -1; + } else if (rankLhs > rankRhs) { + return 1; + } else { + // Same rank, so it's two contact records for the same contact. + // Perform secondary sorting on the phone type. Always place + // mobile before everything else. + final int phoneTypeLhs = (int) lhs[ContactUtil.INDEX_PHONE_EMAIL_TYPE]; + final int phoneTypeRhs = (int) rhs[ContactUtil.INDEX_PHONE_EMAIL_TYPE]; + if (phoneTypeLhs == Phone.TYPE_MOBILE && + phoneTypeRhs == Phone.TYPE_MOBILE) { + return 0; + } else if (phoneTypeLhs == Phone.TYPE_MOBILE) { + return -1; + } else if (phoneTypeRhs == Phone.TYPE_MOBILE) { + return 1; + } else { + // Use the default sort order, i.e. sort by phoneType value. + return phoneTypeLhs < phoneTypeRhs ? -1 : + (phoneTypeLhs == phoneTypeRhs ? 0 : 1); + } + } + } + }); + + // Finally, add all the rows to this cursor. + for (final Object[] row : rows) { + retCursor.addRow(row); + } + return retCursor; + } + return null; + } +} |