diff options
5 files changed, 119 insertions, 19 deletions
diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java index a3bed8c3..9a5b7c48 100644 --- a/src/com/android/providers/contacts/CallLogProvider.java +++ b/src/com/android/providers/contacts/CallLogProvider.java @@ -46,11 +46,11 @@ import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; import com.android.providers.contacts.CallLogDatabaseHelper.DbProperties; import com.android.providers.contacts.CallLogDatabaseHelper.Tables; import com.android.providers.contacts.util.SelectionBuilder; import com.android.providers.contacts.util.UserUtils; -import com.google.common.annotations.VisibleForTesting; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -153,6 +153,19 @@ public class CallLogProvider extends ContentProvider { sCallsProjectionMap.put(Calls.LAST_MODIFIED, Calls.LAST_MODIFIED); } + private static final String ALLOWED_PACKAGE_FOR_TESTING = "com.android.providers.contacts"; + + @VisibleForTesting + static final String PARAM_KEY_QUERY_FOR_TESTING = "query_for_testing"; + + /** + * A long to override the clock used for timestamps, or "null" to reset to the system clock. + */ + @VisibleForTesting + static final String PARAM_KEY_SET_TIME_FOR_TESTING = "set_time_for_testing"; + + private static Long sTimeForTestMillis; + private HandlerThread mBackgroundThread; private Handler mBackgroundHandler; private volatile CountDownLatch mReadAccessLatch; @@ -223,6 +236,9 @@ public class CallLogProvider extends ContentProvider { " order=[" + sortOrder + "] CPID=" + Binder.getCallingPid() + " User=" + UserUtils.getCurrentUserHandle(getContext())); } + + queryForTesting(uri); + waitForAccess(mReadAccessLatch); final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(Tables.CALLS); @@ -278,6 +294,30 @@ public class CallLogProvider extends ContentProvider { return c; } + private void queryForTesting(Uri uri) { + if (!uri.getBooleanQueryParameter(PARAM_KEY_QUERY_FOR_TESTING, false)) { + return; + } + if (!getCallingPackage().equals(ALLOWED_PACKAGE_FOR_TESTING)) { + throw new IllegalArgumentException("query_for_testing set from foreign package " + + getCallingPackage()); + } + + String timeString = uri.getQueryParameter(PARAM_KEY_SET_TIME_FOR_TESTING); + if (timeString != null) { + if (timeString.equals("null")) { + sTimeForTestMillis = null; + } else { + sTimeForTestMillis = Long.parseLong(timeString); + } + } + } + + @VisibleForTesting + static Long getTimeForTestMillis() { + return sTimeForTestMillis; + } + /** * Gets an integer query parameter from a given uri. * diff --git a/src/com/android/providers/contacts/DbModifierWithNotification.java b/src/com/android/providers/contacts/DbModifierWithNotification.java index ef1b847b..b3cf0ef4 100644 --- a/src/com/android/providers/contacts/DbModifierWithNotification.java +++ b/src/com/android/providers/contacts/DbModifierWithNotification.java @@ -63,7 +63,8 @@ public class DbModifierWithNotification implements DatabaseModifier { private static final int SOURCE_PACKAGE_COLUMN_INDEX = 0; private static final String NON_NULL_SOURCE_PACKAGE_SELECTION = VoicemailContract.SOURCE_PACKAGE_FIELD + " IS NOT NULL"; - + private static final String NOT_DELETED_SELECTION = + Voicemails.DELETED + " == 0"; private final String mTableName; private final SQLiteDatabase mDb; private final InsertHelper mInsertHelper; @@ -98,7 +99,7 @@ public class DbModifierWithNotification implements DatabaseModifier { public long insert(String table, String nullColumnHack, ContentValues values) { Set<String> packagesModified = getModifiedPackages(values); if (mIsCallsTable) { - values.put(Calls.LAST_MODIFIED, System.currentTimeMillis()); + values.put(Calls.LAST_MODIFIED, getTimeMillis()); } long rowId = mDb.insert(table, nullColumnHack, values); if (rowId > 0 && packagesModified.size() != 0) { @@ -115,7 +116,7 @@ public class DbModifierWithNotification implements DatabaseModifier { public long insert(ContentValues values) { Set<String> packagesModified = getModifiedPackages(values); if (mIsCallsTable) { - values.put(Calls.LAST_MODIFIED, System.currentTimeMillis()); + values.put(Calls.LAST_MODIFIED, getTimeMillis()); } long rowId = mInsertHelper.insert(values); if (rowId > 0 && packagesModified.size() != 0) { @@ -160,8 +161,12 @@ public class DbModifierWithNotification implements DatabaseModifier { boolean hasMarkedRead = false; if (mIsCallsTable) { - values.put(Calls.LAST_MODIFIED, System.currentTimeMillis()); - + if (values.containsKey(Voicemails.DELETED) + && !values.getAsBoolean(Voicemails.DELETED)) { + values.put(Calls.LAST_MODIFIED, getTimeMillis()); + } else { + updateLastModified(table, whereClause, whereArgs); + } if (isVoicemail) { // If a calling package is modifying its own entries, it means that the change came // from the server and thus is synced or "clean". Otherwise, it means that a local @@ -199,6 +204,15 @@ public class DbModifierWithNotification implements DatabaseModifier { return count; } + private void updateLastModified(String table, String whereClause, String[] whereArgs) { + ContentValues values = new ContentValues(); + values.put(Calls.LAST_MODIFIED, getTimeMillis()); + + mDb.update(table, values, + DbQueryUtils.concatenateClauses(NOT_DELETED_SELECTION, whereClause), + whereArgs); + } + @Override public int delete(String table, String whereClause, String[] whereArgs) { Set<String> packagesModified = getModifiedPackages(whereClause, whereArgs); @@ -217,7 +231,7 @@ public class DbModifierWithNotification implements DatabaseModifier { ContentValues values = new ContentValues(); values.put(VoicemailContract.Voicemails.DIRTY, 1); values.put(VoicemailContract.Voicemails.DELETED, 1); - values.put(VoicemailContract.Voicemails.LAST_MODIFIED, System.currentTimeMillis()); + values.put(VoicemailContract.Voicemails.LAST_MODIFIED, getTimeMillis()); count = mDb.update(table, values, whereClause, whereArgs); } else { count = mDb.delete(table, whereClause, whereArgs); @@ -363,4 +377,11 @@ public class DbModifierWithNotification implements DatabaseModifier { } return values.getAsBoolean(key); } + + private long getTimeMillis() { + if (CallLogProvider.getTimeForTestMillis() == null) { + return System.currentTimeMillis(); + } + return CallLogProvider.getTimeForTestMillis(); + } } diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java index 86dff66a..09241549 100644 --- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java +++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java @@ -31,6 +31,7 @@ import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.provider.BaseColumns; import android.provider.CallLog; +import android.provider.CallLog.Calls; import android.provider.ContactsContract; import android.provider.ContactsContract.AggregationExceptions; import android.provider.ContactsContract.CommonDataKinds.Email; @@ -1355,16 +1356,21 @@ public abstract class BaseContactsProvider2Test extends PhotoLoadingTestCase { } } - protected void assertLastModified(Uri uri) { - assertLastModified(uri, System.currentTimeMillis(), 1000); - } - - protected void assertLastModified(Uri uri, long time, long tolerance) { + protected void assertLastModified(Uri uri, long time) { Cursor c = mResolver.query(uri, null, null, null, null); c.moveToFirst(); int index = c.getColumnIndex(CallLog.Calls.LAST_MODIFIED); long timeStamp = c.getLong(index); - assertTrue(Math.abs(time - timeStamp) < tolerance); + assertEquals(timeStamp, time); + } + + protected void setTimeForTest(Long time) { + Uri uri = Calls.CONTENT_URI.buildUpon() + .appendQueryParameter(CallLogProvider.PARAM_KEY_QUERY_FOR_TESTING, "1") + .appendQueryParameter(CallLogProvider.PARAM_KEY_SET_TIME_FOR_TESTING, + time == null ? "null" : time.toString()) + .build(); + mResolver.query(uri, null, null, null, null); } /** * A contact in the database, and the attributes used to create it. Construct using diff --git a/tests/src/com/android/providers/contacts/CallLogProviderTest.java b/tests/src/com/android/providers/contacts/CallLogProviderTest.java index f492e736..8ee7a5be 100644 --- a/tests/src/com/android/providers/contacts/CallLogProviderTest.java +++ b/tests/src/com/android/providers/contacts/CallLogProviderTest.java @@ -85,16 +85,18 @@ public class CallLogProviderTest extends BaseContactsProvider2Test { protected void tearDown() throws Exception { setUpWithVoicemailPermissions(); mResolver.delete(Calls.CONTENT_URI_WITH_VOICEMAIL, null, null); + setTimeForTest(null); super.tearDown(); } public void testInsert_RegularCallRecord() { + setTimeForTest(1000L); ContentValues values = getDefaultCallValues(); Uri uri = mResolver.insert(Calls.CONTENT_URI, values); values.put(Calls.COUNTRY_ISO, "us"); assertStoredValues(uri, values); assertSelection(uri, values, Calls._ID, ContentUris.parseId(uri)); - assertLastModified(uri); + assertLastModified(uri, 1000); } private void setUpWithVoicemailPermissions() { @@ -105,6 +107,7 @@ public class CallLogProviderTest extends BaseContactsProvider2Test { public void testInsert_VoicemailCallRecord() { setUpWithVoicemailPermissions(); + setTimeForTest(1000L); final ContentValues values = getDefaultCallValues(); values.put(Calls.TYPE, Calls.VOICEMAIL_TYPE); values.put(Calls.VOICEMAIL_URI, "content://foo/voicemail/2"); @@ -121,10 +124,11 @@ public class CallLogProviderTest extends BaseContactsProvider2Test { Uri uri = mResolver.insert(Calls.CONTENT_URI_WITH_VOICEMAIL, values); assertStoredValues(uri, values); assertSelection(uri, values, Calls._ID, ContentUris.parseId(uri)); - assertLastModified(uri); + assertLastModified(uri, 1000); } public void testUpdate() { + setTimeForTest(1000L); Uri uri = insertCallRecord(); ContentValues values = new ContentValues(); values.put(Calls.TYPE, Calls.OUTGOING_TYPE); @@ -139,7 +143,7 @@ public class CallLogProviderTest extends BaseContactsProvider2Test { int count = mResolver.update(uri, values, null, null); assertEquals(1, count); assertStoredValues(uri, values); - assertLastModified(uri); + assertLastModified(uri, 1000); } public void testDelete() { diff --git a/tests/src/com/android/providers/contacts/VoicemailProviderTest.java b/tests/src/com/android/providers/contacts/VoicemailProviderTest.java index b4c9e049..72667314 100644 --- a/tests/src/com/android/providers/contacts/VoicemailProviderTest.java +++ b/tests/src/com/android/providers/contacts/VoicemailProviderTest.java @@ -20,8 +20,10 @@ import android.content.ContentUris; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; +import android.os.BatteryStats.Uid.Proc; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.provider.CallLog; import android.provider.CallLog.Calls; import android.provider.VoicemailContract; import android.provider.VoicemailContract.Status; @@ -65,6 +67,7 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest { protected void setUp() throws Exception { super.setUp(); setUpForOwnPermission(); + addProvider(CallLogProviderTestable.class, CallLog.AUTHORITY); } /** Returns the appropriate /voicemail URI. */ @@ -80,6 +83,7 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest { } public void testInsert() throws Exception { + setTimeForTest(1000L); Uri uri = mResolver.insert(voicemailUri(), getTestVoicemailValues()); // We create on purpose a new set of ContentValues here, because the code above modifies // the copy it gets. @@ -87,7 +91,7 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest { assertSelection(uri, getTestVoicemailValues(), Voicemails._ID, ContentUris.parseId(uri)); assertEquals(1, countFilesInTestDirectory()); - assertLastModified(uri); + assertLastModified(uri, 1000); } public void testInsertReadMessageIsNotNew() throws Exception { @@ -131,6 +135,7 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest { } public void testUpdate() { + setTimeForTest(1000L); Uri uri = insertVoicemail(); ContentValues values = new ContentValues(); values.put(Voicemails.NUMBER, "1-800-263-7643"); @@ -145,7 +150,7 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest { int count = mResolver.update(uri, values, null, null); assertEquals(1, count); assertStoredValues(uri, values); - assertLastModified(uri); + assertLastModified(uri, 1000); } public void testUpdateOwnPackageVoicemail_NotDirty() { @@ -182,6 +187,7 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest { public void testDeleteOtherPackageVoicemail_SetsDirtyStatus() { setUpForFullPermission(); + setTimeForTest(1000L); final Uri anotherVoicemail = insertVoicemailForSourcePackage("another-package"); assertEquals(1, getCount(voicemailUri(), null, null)); @@ -195,7 +201,7 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest { assertEquals(1, getCount(anotherVoicemail, null, null)); assertStoredValues(anotherVoicemail, values); - assertLastModified(anotherVoicemail); + assertLastModified(anotherVoicemail, 1000); } public void testDelete() { @@ -206,6 +212,29 @@ public class VoicemailProviderTest extends BaseVoicemailProviderTest { assertEquals(0, getCount(uri, null, null)); } + public void testUpdateAfterDelete_lastModifiedNotChanged() { + setUpForFullPermission(); + setTimeForTest(1000L); + final Uri anotherVoicemail = insertVoicemailForSourcePackage("another-package"); + assertEquals(1, getCount(voicemailUri(), null, null)); + + // Clear the mapping for our own UID so that this doesn't look like an internal transaction. + mPackageManager.removePackage(Process.myUid()); + mResolver.delete(anotherVoicemail, null, null); + assertLastModified(anotherVoicemail, 1000); + + mPackageManager.addPackage(Process.myUid(), mActor.packageName); + setTimeForTest(2000L); + mResolver.update(anotherVoicemail, new ContentValues(), null, null); + assertLastModified(anotherVoicemail, 1000); + + setTimeForTest(3000L); + ContentValues values = new ContentValues(); + values.put(Voicemails.DELETED, "0"); + mResolver.update(anotherVoicemail, values, null, null); + assertLastModified(anotherVoicemail, 3000); + } + public void testGetType_ItemUri() throws Exception { // Random item uri. assertEquals(Voicemails.ITEM_TYPE, |