Initial checkin of AOSP Messaging app.
b/23110861 Change-Id: I11db999bd10656801e618f78ab2b2ef74136fff1
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user