diff options
author | Yorke Lee <yorkelee@google.com> | 2013-07-18 09:37:33 -0700 |
---|---|---|
committer | Yorke Lee <yorkelee@google.com> | 2013-07-24 15:59:58 -0700 |
commit | 81fea08280784b319b936a3506788d595c6ce2ad (patch) | |
tree | 9a520e09d4f5a84bfa7a5799abcbab00e92f913f | |
parent | 859c719200467b6300d2148ee61258a35bd4b7ee (diff) | |
download | ContactsProvider-81fea08280784b319b936a3506788d595c6ce2ad.tar.gz |
Add pinning support in ContactsProvider
Change-Id: I3c835c2fd0faf99f8fb176752cfcb12e011095a2
4 files changed, 446 insertions, 15 deletions
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java index 34988b66..e0ac81d7 100644 --- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java +++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java @@ -63,6 +63,7 @@ import android.provider.ContactsContract.FullNameStyle; import android.provider.ContactsContract.Groups; import android.provider.ContactsContract.PhoneticNameStyle; import android.provider.ContactsContract.PhotoFiles; +import android.provider.ContactsContract.PinnedPositions; import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.Settings; import android.provider.ContactsContract.StatusUpdates; @@ -114,7 +115,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { * 800-899 Key Lime Pie * </pre> */ - static final int DATABASE_VERSION = 801; + static final int DATABASE_VERSION = 802; private static final String DATABASE_NAME = "contacts2.db"; private static final String DATABASE_PRESENCE = "presence_db"; @@ -361,6 +362,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { public static final String CONCRETE_LAST_TIME_CONTACTED = Tables.CONTACTS + "." + Contacts.LAST_TIME_CONTACTED; public static final String CONCRETE_STARRED = Tables.CONTACTS + "." + Contacts.STARRED; + public static final String CONCRETE_PINNED = Tables.CONTACTS + "." + Contacts.PINNED; public static final String CONCRETE_CUSTOM_RINGTONE = Tables.CONTACTS + "." + Contacts.CUSTOM_RINGTONE; public static final String CONCRETE_SEND_TO_VOICEMAIL = Tables.CONTACTS + "." @@ -407,6 +409,8 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { Tables.RAW_CONTACTS + "." + RawContacts.TIMES_CONTACTED; public static final String CONCRETE_STARRED = Tables.RAW_CONTACTS + "." + RawContacts.STARRED; + public static final String CONCRETE_PINNED = + Tables.RAW_CONTACTS + "." + RawContacts.PINNED; public static final String DISPLAY_NAME = RawContacts.DISPLAY_NAME_PRIMARY; public static final String DISPLAY_NAME_SOURCE = RawContacts.DISPLAY_NAME_SOURCE; @@ -977,6 +981,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { Contacts.TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," + Contacts.LAST_TIME_CONTACTED + " INTEGER," + Contacts.STARRED + " INTEGER NOT NULL DEFAULT 0," + + Contacts.PINNED + " INTEGER NOT NULL DEFAULT " + PinnedPositions.UNPINNED + "," + Contacts.HAS_PHONE_NUMBER + " INTEGER NOT NULL DEFAULT 0," + Contacts.LOOKUP_KEY + " TEXT," + ContactsColumns.LAST_STATUS_UPDATE_ID + " INTEGER REFERENCES data(_id)," + @@ -1007,7 +1012,8 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { RawContacts.TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," + RawContacts.LAST_TIME_CONTACTED + " INTEGER," + RawContacts.STARRED + " INTEGER NOT NULL DEFAULT 0," + - RawContacts.DISPLAY_NAME_PRIMARY + " TEXT," + + RawContacts.PINNED + " INTEGER NOT NULL DEFAULT " + PinnedPositions.UNPINNED + + "," + RawContacts.DISPLAY_NAME_PRIMARY + " TEXT," + RawContacts.DISPLAY_NAME_ALTERNATIVE + " TEXT," + RawContacts.DISPLAY_NAME_SOURCE + " INTEGER NOT NULL DEFAULT " + DisplayNameSources.UNDEFINED + "," + @@ -1634,7 +1640,9 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { + ContactsColumns.CONCRETE_TIMES_CONTACTED + " AS " + RawContacts.TIMES_CONTACTED + "," + ContactsColumns.CONCRETE_STARRED - + " AS " + RawContacts.STARRED; + + " AS " + RawContacts.STARRED + "," + + ContactsColumns.CONCRETE_PINNED + + " AS " + RawContacts.PINNED; String contactNameColumns = "name_raw_contact." + RawContacts.DISPLAY_NAME_SOURCE @@ -1701,7 +1709,8 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { + RawContacts.SEND_TO_VOICEMAIL + "," + RawContacts.LAST_TIME_CONTACTED + "," + RawContacts.TIMES_CONTACTED + "," - + RawContacts.STARRED; + + RawContacts.STARRED + "," + + RawContacts.PINNED; String rawContactsSelect = "SELECT " + RawContactsColumns.CONCRETE_ID + " AS " + RawContacts._ID + "," @@ -1741,6 +1750,8 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { + " AS " + Contacts.SEND_TO_VOICEMAIL + ", " + ContactsColumns.CONCRETE_STARRED + " AS " + Contacts.STARRED + ", " + + ContactsColumns.CONCRETE_PINNED + + " AS " + Contacts.PINNED + ", " + ContactsColumns.CONCRETE_TIMES_CONTACTED + " AS " + Contacts.TIMES_CONTACTED; @@ -2494,6 +2505,12 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { oldVersion = 801; } + if (oldVersion < 802) { + upgradeToVersion802(db); + upgradeViewsAndTriggers = true; + oldVersion = 802; + } + if (upgradeViewsAndTriggers) { createContactsViews(db); createGroupsView(db); @@ -3988,6 +4005,13 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { db.execSQL("UPDATE calls SET presentation=4, number='' WHERE number='-3';"); } + private void upgradeToVersion802(SQLiteDatabase db) { + db.execSQL("ALTER TABLE contacts ADD pinned INTEGER NOT NULL DEFAULT " + + ContactsContract.PinnedPositions.UNPINNED + ";"); + db.execSQL("ALTER TABLE raw_contacts ADD pinned INTEGER NOT NULL DEFAULT " + + ContactsContract.PinnedPositions.UNPINNED + ";"); + } + public String extractHandleFromEmailAddress(String email) { Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(email); if (tokens.length == 0) { diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java index cb134376..dc4826eb 100644 --- a/src/com/android/providers/contacts/ContactsProvider2.java +++ b/src/com/android/providers/contacts/ContactsProvider2.java @@ -92,6 +92,7 @@ import android.provider.ContactsContract.DisplayPhoto; import android.provider.ContactsContract.Groups; import android.provider.ContactsContract.PhoneLookup; import android.provider.ContactsContract.PhotoFiles; +import android.provider.ContactsContract.PinnedPositions; import android.provider.ContactsContract.Profile; import android.provider.ContactsContract.ProviderStatus; import android.provider.ContactsContract.RawContacts; @@ -179,6 +180,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.CountDownLatch; @@ -378,6 +380,8 @@ public class ContactsProvider2 extends AbstractContactsProvider private static final int DELETED_CONTACTS = 23000; private static final int DELETED_CONTACTS_ID = 23001; + private static final int PINNED_POSITION_UPDATE = 24001; + // Inserts into URIs in this map will direct to the profile database if the parent record's // value (looked up from the ContentValues object with the key specified by the value in this // map) is in the profile ID-space (see {@link ProfileDatabaseHelper#PROFILE_ID_SPACE}). @@ -584,6 +588,7 @@ public class ContactsProvider2 extends AbstractContactsProvider .add(ContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE) .add(ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE) .add(Contacts.STARRED) + .add(Contacts.PINNED) .add(Contacts.TIMES_CONTACTED) .add(Contacts.HAS_PHONE_NUMBER) .add(Contacts.CONTACT_LAST_UPDATED_TIMESTAMP) @@ -785,6 +790,7 @@ public class ContactsProvider2 extends AbstractContactsProvider .add(RawContacts.CUSTOM_RINGTONE) .add(RawContacts.SEND_TO_VOICEMAIL) .add(RawContacts.STARRED) + .add(RawContacts.PINNED) .add(RawContacts.AGGREGATION_MODE) .add(RawContacts.RAW_CONTACT_IS_USER_PROFILE) .addAll(sRawContactColumns) @@ -1255,6 +1261,9 @@ public class ContactsProvider2 extends AbstractContactsProvider matcher.addURI(ContactsContract.AUTHORITY, "deleted_contacts", DELETED_CONTACTS); matcher.addURI(ContactsContract.AUTHORITY, "deleted_contacts/#", DELETED_CONTACTS_ID); + + matcher.addURI(ContactsContract.AUTHORITY, "pinned_position_update", + PINNED_POSITION_UPDATE); } private static class DirectoryInfo { @@ -4029,6 +4038,13 @@ public class ContactsProvider2 extends AbstractContactsProvider break; } + case PINNED_POSITION_UPDATE: { + final boolean forceStarWhenPinning = uri.getBooleanQueryParameter( + PinnedPositions.STAR_WHEN_PINNING, false); + count = handlePinningUpdate(values, forceStarWhenPinning); + break; + } + default: { mSyncToNetwork = true; return mLegacyApiSupport.update(uri, values, selection, selectionArgs); @@ -4357,6 +4373,7 @@ public class ContactsProvider2 extends AbstractContactsProvider flagIsSet(values, RawContacts.STARRED)); } mAggregator.get().updateStarred(rawContactId); + mAggregator.get().updatePinned(rawContactId); } else { // if this raw contact is being associated with an account, then update the // favorites group membership based on whether or not this contact is starred. @@ -4489,6 +4506,8 @@ public class ContactsProvider2 extends AbstractContactsProvider values, Contacts.TIMES_CONTACTED); ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.STARRED, values, Contacts.STARRED); + ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.PINNED, + values, Contacts.PINNED); // Nothing to update - just return if (mValues.size() == 0) { @@ -4533,6 +4552,8 @@ public class ContactsProvider2 extends AbstractContactsProvider values, Contacts.TIMES_CONTACTED); ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.STARRED, values, Contacts.STARRED); + ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.PINNED, + values, Contacts.PINNED); mValues.put(Contacts.CONTACT_LAST_UPDATED_TIMESTAMP, Clock.getInstance().currentTimeMillis()); @@ -8482,6 +8503,68 @@ public class ContactsProvider2 extends AbstractContactsProvider // getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_VOICE_CALLS); } + /** + * Handles pinning update information from clients. + * + * @param values ContentValues containing key-value pairs where keys correspond to + * the contactId for which to update the pinnedPosition, and the value is the actual + * pinned position (a positive integer). + * @return The number of contacts that had their pinned positions updated. + */ + private int handlePinningUpdate(ContentValues values, boolean forceStarWhenPinning) { + if (values.size() == 0) return 0; + final SQLiteDatabase db = mDbHelper.get().getWritableDatabase(); + final String[] args; + if (forceStarWhenPinning) { + args = new String[3]; + } else { + args = new String[2]; + } + + final StringBuilder sb = new StringBuilder(); + + sb.append("UPDATE " + Tables.CONTACTS + " SET " + Contacts.PINNED + "=?2"); + if (forceStarWhenPinning) { + sb.append("," + Contacts.STARRED + "=?3"); + } + sb.append(" WHERE " + Contacts._ID + " =?1;"); + final String contactSQL = sb.toString(); + + sb.setLength(0); + sb.append("UPDATE " + Tables.RAW_CONTACTS + " SET " + RawContacts.PINNED + "=?2"); + if (forceStarWhenPinning) { + sb.append("," + RawContacts.STARRED + "=?3"); + } + sb.append(" WHERE " + RawContacts.CONTACT_ID + " =?1;"); + final String rawContactSQL = sb.toString(); + + int count = 0; + for (String id : values.keySet()) { + count++; + final long contactId; + try { + contactId = Integer.valueOf(id); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("contactId must be a positive integer. Found: " + + id); + } + + final Integer pinnedPosition = values.getAsInteger(id); + if (pinnedPosition == null || pinnedPosition < 0) { + throw new IllegalArgumentException("Pinned position must be a positive integer."); + } + args[0] = String.valueOf(contactId); + args[1] = String.valueOf(pinnedPosition); + if (forceStarWhenPinning) { + args[2] = (pinnedPosition == PinnedPositions.UNPINNED ? "0" : "1"); + } + db.execSQL(contactSQL, args); + + db.execSQL(rawContactSQL, args); + } + return count; + } + private boolean handleDataUsageFeedback(Uri uri) { final long currentTimeMillis = Clock.getInstance().currentTimeMillis(); final String usageType = uri.getQueryParameter(DataUsageFeedback.USAGE_TYPE); diff --git a/src/com/android/providers/contacts/aggregation/ContactAggregator.java b/src/com/android/providers/contacts/aggregation/ContactAggregator.java index 6c53fa0f..db88c10b 100644 --- a/src/com/android/providers/contacts/aggregation/ContactAggregator.java +++ b/src/com/android/providers/contacts/aggregation/ContactAggregator.java @@ -33,6 +33,7 @@ import android.provider.ContactsContract.Data; import android.provider.ContactsContract.DisplayNameSources; import android.provider.ContactsContract.FullNameStyle; import android.provider.ContactsContract.PhotoFiles; +import android.provider.ContactsContract.PinnedPositions; import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.StatusUpdates; import android.text.TextUtils; @@ -152,11 +153,13 @@ public class ContactAggregator { private SQLiteStatement mDisplayNameUpdate; private SQLiteStatement mLookupKeyUpdate; private SQLiteStatement mStarredUpdate; + private SQLiteStatement mPinnedUpdate; private SQLiteStatement mContactIdAndMarkAggregatedUpdate; private SQLiteStatement mContactIdUpdate; private SQLiteStatement mMarkAggregatedUpdate; private SQLiteStatement mContactUpdate; private SQLiteStatement mContactInsert; + private SQLiteStatement mResetPinnedForRawContact; private HashMap<Long, Integer> mRawContactsMarkedForAggregation = Maps.newHashMap(); @@ -333,6 +336,11 @@ public class ContactAggregator { + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + " AND " + RawContacts.STARRED + "=1)" + " WHERE " + Contacts._ID + "=?"); + mPinnedUpdate = db.compileStatement("UPDATE " + Tables.CONTACTS + " SET " + + Contacts.PINNED + "=(SELECT MIN(" + RawContacts.PINNED + ") FROM " + + Tables.RAW_CONTACTS + " WHERE " + RawContacts.CONTACT_ID + "=" + + ContactsColumns.CONCRETE_ID + ") WHERE " + Contacts._ID + "=?"); + mContactIdAndMarkAggregatedUpdate = db.compileStatement( "UPDATE " + Tables.RAW_CONTACTS + " SET " + RawContacts.CONTACT_ID + "=?, " @@ -357,6 +365,11 @@ public class ContactAggregator { mContactUpdate = db.compileStatement(ContactReplaceSqlStatement.UPDATE_SQL); mContactInsert = db.compileStatement(ContactReplaceSqlStatement.INSERT_SQL); + mResetPinnedForRawContact = db.compileStatement( + "UPDATE " + Tables.RAW_CONTACTS + + " SET " + RawContacts.PINNED + "=" + PinnedPositions.UNPINNED + + " WHERE " + RawContacts._ID + "=?"); + mMimeTypeIdEmail = mDbHelper.getMimeTypeId(Email.CONTENT_ITEM_TYPE); mMimeTypeIdIdentity = mDbHelper.getMimeTypeId(Identity.CONTENT_ITEM_TYPE); mMimeTypeIdPhoto = mDbHelper.getMimeTypeId(Photo.CONTENT_ITEM_TYPE); @@ -989,10 +1002,13 @@ public class ContactAggregator { } /** - * Creates a stand-alone Contact for the given raw contact ID. + * Creates a stand-alone Contact for the given raw contact ID. This is only called + * when splitting an existing merged contact into separate raw contacts. */ private void createNewContactForRawContact( TransactionContext txContext, SQLiteDatabase db, long rawContactId) { + // All split contacts should automatically be unpinned. + unpinRawContact(rawContactId); mSelectionArgs1[0] = String.valueOf(rawContactId); computeAggregateData(db, mRawContactsQueryByRawContactId, mSelectionArgs1, mContactInsert); @@ -1097,6 +1113,11 @@ public class ContactAggregator { mPresenceContactIdUpdate.execute(); } + private void unpinRawContact(long rawContactId) { + mResetPinnedForRawContact.bindLong(1, rawContactId); + mResetPinnedForRawContact.execute(); + } + interface AggregateExceptionPrefetchQuery { String TABLE = Tables.AGGREGATION_EXCEPTIONS; @@ -1705,6 +1726,7 @@ public class ContactAggregator { + RawContacts.LAST_TIME_CONTACTED + "," + RawContacts.TIMES_CONTACTED + "," + RawContacts.STARRED + "," + + RawContacts.PINNED + "," + RawContacts.NAME_VERIFIED + "," + DataColumns.CONCRETE_ID + "," + DataColumns.CONCRETE_MIMETYPE_ID + "," @@ -1740,11 +1762,12 @@ public class ContactAggregator { int LAST_TIME_CONTACTED = 9; int TIMES_CONTACTED = 10; int STARRED = 11; - int NAME_VERIFIED = 12; - int DATA_ID = 13; - int MIMETYPE_ID = 14; - int IS_SUPER_PRIMARY = 15; - int PHOTO_FILE_ID = 16; + int PINNED = 12; + int NAME_VERIFIED = 13; + int DATA_ID = 14; + int MIMETYPE_ID = 15; + int IS_SUPER_PRIMARY = 16; + int PHOTO_FILE_ID = 17; } private interface ContactReplaceSqlStatement { @@ -1759,6 +1782,7 @@ public class ContactAggregator { + Contacts.LAST_TIME_CONTACTED + "=?, " + Contacts.TIMES_CONTACTED + "=?, " + Contacts.STARRED + "=?, " + + Contacts.PINNED + "=?, " + Contacts.HAS_PHONE_NUMBER + "=?, " + Contacts.LOOKUP_KEY + "=?, " + Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + "=? " + @@ -1774,11 +1798,12 @@ public class ContactAggregator { + Contacts.LAST_TIME_CONTACTED + ", " + Contacts.TIMES_CONTACTED + ", " + Contacts.STARRED + ", " + + Contacts.PINNED + ", " + Contacts.HAS_PHONE_NUMBER + ", " + Contacts.LOOKUP_KEY + ", " + Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + ") " + - " VALUES (?,?,?,?,?,?,?,?,?,?,?)"; + " VALUES (?,?,?,?,?,?,?,?,?,?,?,?)"; int NAME_RAW_CONTACT_ID = 1; int PHOTO_ID = 2; @@ -1788,10 +1813,11 @@ public class ContactAggregator { int LAST_TIME_CONTACTED = 6; int TIMES_CONTACTED = 7; int STARRED = 8; - int HAS_PHONE_NUMBER = 9; - int LOOKUP_KEY = 10; - int CONTACT_LAST_UPDATED_TIMESTAMP = 11; - int CONTACT_ID = 12; + int PINNED = 9; + int HAS_PHONE_NUMBER = 10; + int LOOKUP_KEY = 11; + int CONTACT_LAST_UPDATED_TIMESTAMP = 12; + int CONTACT_ID = 13; } /** @@ -1830,6 +1856,7 @@ public class ContactAggregator { long contactLastTimeContacted = 0; int contactTimesContacted = 0; int contactStarred = 0; + int contactPinned = PinnedPositions.UNPINNED; int hasPhoneNumber = 0; StringBuilder lookupKey = new StringBuilder(); @@ -1886,6 +1913,11 @@ public class ContactAggregator { contactStarred = 1; } + // contactPinned should be the lowest value of its constituent raw contacts, + // excluding 0 + final int rawContactPinned = c.getInt(RawContactsQuery.PINNED); + contactPinned = Math.min(contactPinned, rawContactPinned); + appendLookupKey( lookupKey, accountWithDataSet, @@ -1951,6 +1983,8 @@ public class ContactAggregator { contactTimesContacted); statement.bindLong(ContactReplaceSqlStatement.STARRED, contactStarred); + statement.bindLong(ContactReplaceSqlStatement.PINNED, + contactPinned); statement.bindLong(ContactReplaceSqlStatement.HAS_PHONE_NUMBER, hasPhoneNumber); statement.bindString(ContactReplaceSqlStatement.LOOKUP_KEY, @@ -2324,6 +2358,20 @@ public class ContactAggregator { } /** + * Execute {@link SQLiteStatement} that will update the + * {@link Contacts#PINNED} flag for the given {@link RawContacts#_ID}. + */ + public void updatePinned(long rawContactId) { + long contactId = mDbHelper.getContactId(rawContactId); + if (contactId == 0) { + return; + } + + mPinnedUpdate.bindLong(1, contactId); + mPinnedUpdate.execute(); + } + + /** * Finds matching contacts and returns a cursor on those. */ public Cursor queryAggregationSuggestions(SQLiteQueryBuilder qb, diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java index 3c2b114d..1876589e 100644 --- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java +++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java @@ -54,6 +54,7 @@ import android.provider.ContactsContract.FullNameStyle; import android.provider.ContactsContract.Groups; import android.provider.ContactsContract.PhoneLookup; import android.provider.ContactsContract.PhoneticNameStyle; +import android.provider.ContactsContract.PinnedPositions; import android.provider.ContactsContract.Profile; import android.provider.ContactsContract.ProviderStatus; import android.provider.ContactsContract.RawContacts; @@ -129,6 +130,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { Contacts.LAST_TIME_CONTACTED, Contacts.TIMES_CONTACTED, Contacts.STARRED, + Contacts.PINNED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, Contacts.PHOTO_FILE_ID, @@ -168,6 +170,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { Contacts.LAST_TIME_CONTACTED, Contacts.TIMES_CONTACTED, Contacts.STARRED, + Contacts.PINNED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, Contacts.PHOTO_FILE_ID, @@ -211,6 +214,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { Contacts.LAST_TIME_CONTACTED, Contacts.TIMES_CONTACTED, Contacts.STARRED, + Contacts.PINNED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, Contacts.PHOTO_FILE_ID, @@ -256,6 +260,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { Contacts.LAST_TIME_CONTACTED, Contacts.TIMES_CONTACTED, Contacts.STARRED, + Contacts.PINNED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, Contacts.PHOTO_FILE_ID, @@ -309,6 +314,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { RawContacts.CUSTOM_RINGTONE, RawContacts.SEND_TO_VOICEMAIL, RawContacts.STARRED, + RawContacts.PINNED, RawContacts.AGGREGATION_MODE, RawContacts.SYNC1, RawContacts.SYNC2, @@ -379,6 +385,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { Contacts.LAST_TIME_CONTACTED, Contacts.TIMES_CONTACTED, Contacts.STARRED, + Contacts.PINNED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, Contacts.PHOTO_FILE_ID, @@ -455,6 +462,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { Contacts.LAST_TIME_CONTACTED, Contacts.TIMES_CONTACTED, Contacts.STARRED, + Contacts.PINNED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, Contacts.PHOTO_FILE_ID, @@ -544,6 +552,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { Contacts.LAST_TIME_CONTACTED, Contacts.TIMES_CONTACTED, Contacts.STARRED, + Contacts.PINNED, Contacts.IN_VISIBLE_GROUP, Contacts.PHOTO_ID, Contacts.PHOTO_FILE_ID, @@ -7708,10 +7717,277 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test { return ids; } + /** * End delta api tests. ******************************************************/ + /******************************************************* + * Pinning support tests + */ + public void testPinnedPositionsUpdateForceStar() { + final DatabaseAsserts.ContactIdPair i1 = DatabaseAsserts.assertAndCreateContact(mResolver); + final DatabaseAsserts.ContactIdPair i2 = DatabaseAsserts.assertAndCreateContact(mResolver); + final DatabaseAsserts.ContactIdPair i3 = DatabaseAsserts.assertAndCreateContact(mResolver); + final DatabaseAsserts.ContactIdPair i4 = DatabaseAsserts.assertAndCreateContact(mResolver); + + final int unpinned = PinnedPositions.UNPINNED; + + assertStoredValuesWithProjection(Contacts.CONTENT_URI, + cv(Contacts._ID, i1.mContactId, Contacts.PINNED, unpinned, Contacts.STARRED, 0), + cv(Contacts._ID, i2.mContactId, Contacts.PINNED, unpinned, Contacts.STARRED, 0), + cv(Contacts._ID, i3.mContactId, Contacts.PINNED, unpinned, Contacts.STARRED, 0), + cv(Contacts._ID, i4.mContactId, Contacts.PINNED, unpinned, Contacts.STARRED, 0) + ); + + assertStoredValuesWithProjection(RawContacts.CONTENT_URI, + cv(RawContacts._ID, i1.mRawContactId, RawContacts.PINNED, unpinned), + cv(RawContacts._ID, i2.mRawContactId, RawContacts.PINNED, unpinned), + cv(RawContacts._ID, i3.mRawContactId, RawContacts.PINNED, unpinned), + cv(RawContacts._ID, i4.mRawContactId, RawContacts.PINNED, unpinned) + ); + + final ContentValues values = cv(i1.mContactId, 1, i3.mContactId, 3, i4.mContactId, 2); + mResolver.update(ContactsContract.PinnedPositions.UPDATE_URI.buildUpon() + .appendQueryParameter(PinnedPositions.STAR_WHEN_PINNING, "true").build(), + values, null, null); + + // Pinning a contact should automatically star it if we specified the boolean parameter + + assertStoredValuesWithProjection(Contacts.CONTENT_URI, + cv(Contacts._ID, i1.mContactId, Contacts.PINNED, 1, Contacts.STARRED, 1), + cv(Contacts._ID, i2.mContactId, Contacts.PINNED, unpinned, Contacts.STARRED, 0), + cv(Contacts._ID, i3.mContactId, Contacts.PINNED, 3, Contacts.STARRED, 1), + cv(Contacts._ID, i4.mContactId, Contacts.PINNED, 2, Contacts.STARRED, 1) + ); + + // Make sure the values are propagated to raw contacts + + assertStoredValuesWithProjection(RawContacts.CONTENT_URI, + cv(RawContacts._ID, i1.mRawContactId, RawContacts.PINNED, 1), + cv(RawContacts._ID, i2.mRawContactId, RawContacts.PINNED, unpinned), + cv(RawContacts._ID, i3.mRawContactId, RawContacts.PINNED, 3), + cv(RawContacts._ID, i4.mRawContactId, RawContacts.PINNED, 2) + ); + + final ContentValues unpin = cv(i3.mContactId, unpinned); + mResolver.update(ContactsContract.PinnedPositions.UPDATE_URI.buildUpon() + .appendQueryParameter(PinnedPositions.STAR_WHEN_PINNING, "true").build(), + unpin, null, null); + + // Unpinning a contact should automatically unstar it + assertStoredValuesWithProjection(Contacts.CONTENT_URI, + cv(Contacts._ID, i1.mContactId, Contacts.PINNED, 1, Contacts.STARRED, 1), + cv(Contacts._ID, i2.mContactId, Contacts.PINNED, unpinned, Contacts.STARRED, 0), + cv(Contacts._ID, i3.mContactId, Contacts.PINNED, unpinned, Contacts.STARRED, 0), + cv(Contacts._ID, i4.mContactId, Contacts.PINNED, 2, Contacts.STARRED, 1) + ); + + assertStoredValuesWithProjection(RawContacts.CONTENT_URI, + cv(Contacts._ID, i1.mRawContactId, RawContacts.PINNED, 1, RawContacts.STARRED, 1), + cv(Contacts._ID, i2.mRawContactId, RawContacts.PINNED, unpinned, + RawContacts.STARRED, 0), + cv(Contacts._ID, i3.mRawContactId, RawContacts.PINNED, unpinned, + RawContacts.STARRED, 0), + cv(Contacts._ID, i4.mRawContactId, RawContacts.PINNED, 2, RawContacts.STARRED, 1) + ); + } + + public void testPinnedPositionsUpdateDontForceStar() { + final DatabaseAsserts.ContactIdPair i1 = DatabaseAsserts.assertAndCreateContact(mResolver); + final DatabaseAsserts.ContactIdPair i2 = DatabaseAsserts.assertAndCreateContact(mResolver); + final DatabaseAsserts.ContactIdPair i3 = DatabaseAsserts.assertAndCreateContact(mResolver); + final DatabaseAsserts.ContactIdPair i4 = DatabaseAsserts.assertAndCreateContact(mResolver); + + final int unpinned = PinnedPositions.UNPINNED; + + final ContentValues values = cv(i1.mContactId, 1, i3.mContactId, 3, i4.mContactId, 2); + mResolver.update(ContactsContract.PinnedPositions.UPDATE_URI, values, null, null); + + // Pinning a contact should not automatically star it + + assertStoredValuesWithProjection(Contacts.CONTENT_URI, + cv(Contacts._ID, i1.mContactId, Contacts.PINNED, 1, Contacts.STARRED, 0), + cv(Contacts._ID, i2.mContactId, Contacts.PINNED, unpinned, Contacts.STARRED, 0), + cv(Contacts._ID, i3.mContactId, Contacts.PINNED, 3, Contacts.STARRED, 0), + cv(Contacts._ID, i4.mContactId, Contacts.PINNED, 2, Contacts.STARRED, 0) + ); + + // Make sure the values are propagated to raw contacts + + assertStoredValuesWithProjection(RawContacts.CONTENT_URI, + cv(RawContacts._ID, i1.mRawContactId, RawContacts.PINNED, 1, + RawContacts.STARRED, 0), + cv(RawContacts._ID, i2.mRawContactId, RawContacts.PINNED, unpinned, + RawContacts.STARRED, 0), + cv(RawContacts._ID, i3.mRawContactId, RawContacts.PINNED, 3, + RawContacts.STARRED, 0), + cv(RawContacts._ID, i4.mRawContactId, RawContacts.PINNED, 2, + RawContacts.STARRED, 0) + ); + + + // Manually star contact 3 + assertEquals(1, updateItem(Contacts.CONTENT_URI, i3.mContactId, Contacts.STARRED, "1")); + + // Check the third contact and raw contact is starred + assertStoredValuesWithProjection(Contacts.CONTENT_URI, + cv(Contacts._ID, i1.mContactId, Contacts.PINNED, 1, Contacts.STARRED, 0), + cv(Contacts._ID, i2.mContactId, Contacts.PINNED, unpinned, Contacts.STARRED, 0), + cv(Contacts._ID, i3.mContactId, Contacts.PINNED, 3, Contacts.STARRED, 1), + cv(Contacts._ID, i4.mContactId, Contacts.PINNED, 2, Contacts.STARRED, 0) + ); + + assertStoredValuesWithProjection(RawContacts.CONTENT_URI, + cv(RawContacts._ID, i1.mRawContactId, RawContacts.PINNED, 1, + RawContacts.STARRED, 0), + cv(RawContacts._ID, i2.mRawContactId, RawContacts.PINNED, unpinned, + RawContacts.STARRED, 0), + cv(RawContacts._ID, i3.mRawContactId, RawContacts.PINNED, 3, + RawContacts.STARRED, 1), + cv(RawContacts._ID, i4.mRawContactId, RawContacts.PINNED, 2, + RawContacts.STARRED, 0) + ); + + final ContentValues unpin = cv(i3.mContactId, unpinned); + + mResolver.update(ContactsContract.PinnedPositions.UPDATE_URI, unpin, null, null); + + // Unpinning a contact should not automatically unstar it + assertStoredValuesWithProjection(Contacts.CONTENT_URI, + cv(Contacts._ID, i1.mContactId, Contacts.PINNED, 1, Contacts.STARRED, 0), + cv(Contacts._ID, i2.mContactId, Contacts.PINNED, unpinned, Contacts.STARRED, 0), + cv(Contacts._ID, i3.mContactId, Contacts.PINNED, unpinned, Contacts.STARRED, 1), + cv(Contacts._ID, i4.mContactId, Contacts.PINNED, 2, Contacts.STARRED, 0) + ); + + assertStoredValuesWithProjection(RawContacts.CONTENT_URI, + cv(Contacts._ID, i1.mRawContactId, RawContacts.PINNED, 1, RawContacts.STARRED, 0), + cv(Contacts._ID, i2.mRawContactId, RawContacts.PINNED, unpinned, + RawContacts.STARRED, 0), + cv(Contacts._ID, i3.mRawContactId, RawContacts.PINNED, unpinned, + RawContacts.STARRED, 1), + cv(Contacts._ID, i4.mRawContactId, RawContacts.PINNED, 2, RawContacts.STARRED, 0) + ); + } + + public void testPinnedPositionsUpdateIllegalValues() { + final DatabaseAsserts.ContactIdPair i1 = DatabaseAsserts.assertAndCreateContact(mResolver); + final DatabaseAsserts.ContactIdPair i2 = DatabaseAsserts.assertAndCreateContact(mResolver); + final DatabaseAsserts.ContactIdPair i3 = DatabaseAsserts.assertAndCreateContact(mResolver); + final DatabaseAsserts.ContactIdPair i4 = DatabaseAsserts.assertAndCreateContact(mResolver); + + assertStoredValuesWithProjection(Contacts.CONTENT_URI, + cv(Contacts._ID, i1.mContactId, Contacts.PINNED, PinnedPositions.UNPINNED), + cv(Contacts._ID, i2.mContactId, Contacts.PINNED, PinnedPositions.UNPINNED), + cv(Contacts._ID, i3.mContactId, Contacts.PINNED, PinnedPositions.UNPINNED), + cv(Contacts._ID, i4.mContactId, Contacts.PINNED, PinnedPositions.UNPINNED) + ); + + // negative number + final ContentValues values = cv(i1.mContactId, 1, i3.mContactId, 3, i4.mContactId, -2); + try { + mResolver.update(ContactsContract.PinnedPositions.UPDATE_URI, values, null, null); + fail("Pinned position must be a distinct(unrepeated) positive integer."); + } catch (IllegalArgumentException expected) { + } + + // non-integer + final ContentValues values3 = cv(i1.mContactId, "1.1"); + try { + mResolver.update(ContactsContract.PinnedPositions.UPDATE_URI, values, null, null); + fail("Pinned position must be a distinct(unrepeated) positive integer."); + } catch (IllegalArgumentException expected) { + } + + // nothing should have been changed + + assertStoredValuesWithProjection(Contacts.CONTENT_URI, + cv(Contacts._ID, i1.mContactId, Contacts.PINNED, PinnedPositions.UNPINNED), + cv(Contacts._ID, i2.mContactId, Contacts.PINNED, PinnedPositions.UNPINNED), + cv(Contacts._ID, i3.mContactId, Contacts.PINNED, PinnedPositions.UNPINNED), + cv(Contacts._ID, i4.mContactId, Contacts.PINNED, PinnedPositions.UNPINNED) + ); + + assertStoredValuesWithProjection(RawContacts.CONTENT_URI, + cv(RawContacts._ID, i1.mRawContactId, RawContacts.PINNED, PinnedPositions.UNPINNED), + cv(RawContacts._ID, i2.mRawContactId, RawContacts.PINNED, PinnedPositions.UNPINNED), + cv(RawContacts._ID, i3.mRawContactId, RawContacts.PINNED, PinnedPositions.UNPINNED), + cv(RawContacts._ID, i4.mRawContactId, RawContacts.PINNED, PinnedPositions.UNPINNED) + ); + } + + public void testPinnedPositionsAfterJoinAndSplit() { + final DatabaseAsserts.ContactIdPair i1 = DatabaseAsserts.assertAndCreateContact(mResolver); + final DatabaseAsserts.ContactIdPair i2 = DatabaseAsserts.assertAndCreateContact(mResolver); + final DatabaseAsserts.ContactIdPair i3 = DatabaseAsserts.assertAndCreateContact(mResolver); + final DatabaseAsserts.ContactIdPair i4 = DatabaseAsserts.assertAndCreateContact(mResolver); + + final ContentValues values = cv(i1.mContactId, 1, i2.mContactId, 2, i3.mContactId, 3); + mResolver.update(ContactsContract.PinnedPositions.UPDATE_URI.buildUpon() + .appendQueryParameter(PinnedPositions.STAR_WHEN_PINNING, "true").build(), + values, null, null); + + // aggregate raw contact 1 and 4 together. + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, i1.mRawContactId, + i4.mRawContactId); + + // If only one contact is pinned, the resulting contact should inherit the pinned position + assertStoredValuesWithProjection(Contacts.CONTENT_URI, + cv(Contacts._ID, i1.mContactId, Contacts.PINNED, 1), + cv(Contacts._ID, i2.mContactId, Contacts.PINNED, 2), + cv(Contacts._ID, i3.mContactId, Contacts.PINNED, 3) + ); + + assertStoredValuesWithProjection(RawContacts.CONTENT_URI, + cv(RawContacts._ID, i1.mRawContactId, RawContacts.PINNED, 1, + RawContacts.STARRED, 1), + cv(RawContacts._ID, i2.mRawContactId, RawContacts.PINNED, 2, + RawContacts.STARRED, 1), + cv(RawContacts._ID, i3.mRawContactId, RawContacts.PINNED, 3, + RawContacts.STARRED, 1), + cv(RawContacts._ID, i4.mRawContactId, RawContacts.PINNED, PinnedPositions.UNPINNED, + RawContacts.STARRED, 0) + ); + + // aggregate raw contact 2 and 3 together. + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, i2.mRawContactId, + i3.mRawContactId); + + // If both raw contacts are pinned, the resulting contact should inherit the lower + // pinned position + assertStoredValuesWithProjection(Contacts.CONTENT_URI, + cv(Contacts._ID, i1.mContactId, Contacts.PINNED, 1), + cv(Contacts._ID, i2.mContactId, Contacts.PINNED, 2) + ); + + assertStoredValuesWithProjection(RawContacts.CONTENT_URI, + cv(RawContacts._ID, i1.mRawContactId, RawContacts.PINNED, 1), + cv(RawContacts._ID, i2.mRawContactId, RawContacts.PINNED, 2), + cv(RawContacts._ID, i3.mRawContactId, RawContacts.PINNED, 3), + cv(RawContacts._ID, i4.mRawContactId, RawContacts.PINNED, PinnedPositions.UNPINNED) + ); + + // split the aggregated raw contacts + setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, i1.mRawContactId, + i4.mRawContactId); + + // raw contacts should be unpinned after being split, but still starred + assertStoredValuesWithProjection(RawContacts.CONTENT_URI, + cv(RawContacts._ID, i1.mRawContactId, RawContacts.PINNED, PinnedPositions.UNPINNED, + RawContacts.STARRED, 1), + cv(RawContacts._ID, i2.mRawContactId, RawContacts.PINNED, 2, + RawContacts.STARRED, 1), + cv(RawContacts._ID, i3.mRawContactId, RawContacts.PINNED, 3, + RawContacts.STARRED, 1), + cv(RawContacts._ID, i4.mRawContactId, RawContacts.PINNED, PinnedPositions.UNPINNED, + RawContacts.STARRED, 0) + ); + } + + /** + * End pinning support tests + ******************************************************/ private Cursor queryGroupMemberships(Account account) { Cursor c = mResolver.query(TestUtil.maybeAddAccountQueryParameters(Data.CONTENT_URI, |