summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java')
-rw-r--r--src/com/android/messaging/datamodel/FrequentContactsCursorBuilder.java179
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;
+ }
+}