Merge "Don't expose raw usage stats to clients; return "low-res" values."
diff --git a/run-all-tests.sh b/run-all-tests.sh
index 4a64d35..84a2ce3 100755
--- a/run-all-tests.sh
+++ b/run-all-tests.sh
@@ -23,6 +23,7 @@
mmm -j32 packages/providers/ContactsProvider
adb install -t -r -g $ANDROID_PRODUCT_OUT/system/priv-app/ContactsProvider/ContactsProvider.apk
+adb install -t -r -g $ANDROID_PRODUCT_OUT/data/app/ContactsProviderTests/ContactsProviderTests.apk
adb install -t -r -g $ANDROID_PRODUCT_OUT/data/app/ContactsProviderTests2/ContactsProviderTests2.apk
runtest() {
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index b001653..8d0d06a 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -136,7 +136,7 @@
* 1200-1299 O
* </pre>
*/
- static final int DATABASE_VERSION = 1200;
+ static final int DATABASE_VERSION = 1201;
private static final int MINIMUM_SUPPORTED_VERSION = 700;
public interface Tables {
@@ -325,7 +325,12 @@
public static final String ENTITIES = "view_entities";
public static final String RAW_ENTITIES = "view_raw_entities";
public static final String GROUPS = "view_groups";
+
+ /** The data_usage_stat table joined with other tables. */
public static final String DATA_USAGE_STAT = "view_data_usage_stat";
+
+ /** The data_usage_stat table with the low-res columns. */
+ public static final String DATA_USAGE_LR = "view_data_usage";
public static final String STREAM_ITEMS = "view_stream_items";
public static final String METADATA_SYNC = "view_metadata_sync";
public static final String METADATA_SYNC_STATE = "view_metadata_sync_state";
@@ -408,10 +413,12 @@
public static final String CONCRETE_PHOTO_FILE_ID = Tables.CONTACTS + "."
+ Contacts.PHOTO_FILE_ID;
- public static final String CONCRETE_TIMES_CONTACTED = Tables.CONTACTS + "."
- + Contacts.TIMES_CONTACTED;
- public static final String CONCRETE_LAST_TIME_CONTACTED = Tables.CONTACTS + "."
- + Contacts.LAST_TIME_CONTACTED;
+
+ public static final String CONCRETE_RAW_TIMES_CONTACTED = Tables.CONTACTS + "."
+ + Contacts.RAW_TIMES_CONTACTED;
+ public static final String CONCRETE_RAW_LAST_TIME_CONTACTED = Tables.CONTACTS + "."
+ + Contacts.RAW_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 + "."
@@ -456,10 +463,10 @@
Tables.RAW_CONTACTS + "." + RawContacts.CUSTOM_RINGTONE;
public static final String CONCRETE_SEND_TO_VOICEMAIL =
Tables.RAW_CONTACTS + "." + RawContacts.SEND_TO_VOICEMAIL;
- public static final String CONCRETE_LAST_TIME_CONTACTED =
- Tables.RAW_CONTACTS + "." + RawContacts.LAST_TIME_CONTACTED;
- public static final String CONCRETE_TIMES_CONTACTED =
- Tables.RAW_CONTACTS + "." + RawContacts.TIMES_CONTACTED;
+ public static final String CONCRETE_RAW_LAST_TIME_CONTACTED =
+ Tables.RAW_CONTACTS + "." + RawContacts.RAW_LAST_TIME_CONTACTED;
+ public static final String CONCRETE_RAW_TIMES_CONTACTED =
+ Tables.RAW_CONTACTS + "." + RawContacts.RAW_TIMES_CONTACTED;
public static final String CONCRETE_STARRED =
Tables.RAW_CONTACTS + "." + RawContacts.STARRED;
public static final String CONCRETE_PINNED =
@@ -726,14 +733,24 @@
public static final String CONCRETE_DATA_ID = Tables.DATA_USAGE_STAT + "." + DATA_ID;
/** type: INTEGER (long) */
- public static final String LAST_TIME_USED = "last_time_used";
- public static final String CONCRETE_LAST_TIME_USED =
- Tables.DATA_USAGE_STAT + "." + LAST_TIME_USED;
+ public static final String RAW_LAST_TIME_USED = Data.RAW_LAST_TIME_USED;
+ public static final String LR_LAST_TIME_USED = Data.LR_LAST_TIME_USED;
/** type: INTEGER */
- public static final String TIMES_USED = "times_used";
- public static final String CONCRETE_TIMES_USED =
- Tables.DATA_USAGE_STAT + "." + TIMES_USED;
+ public static final String RAW_TIMES_USED = Data.RAW_TIMES_USED;
+ public static final String LR_TIMES_USED = Data.LR_TIMES_USED;
+
+ public static final String CONCRETE_RAW_LAST_TIME_USED =
+ Tables.DATA_USAGE_STAT + "." + RAW_LAST_TIME_USED;
+
+ public static final String CONCRETE_RAW_TIMES_USED =
+ Tables.DATA_USAGE_STAT + "." + RAW_TIMES_USED;
+
+ public static final String CONCRETE_LR_LAST_TIME_USED =
+ Tables.DATA_USAGE_STAT + "." + LR_LAST_TIME_USED;
+
+ public static final String CONCRETE_LR_TIMES_USED =
+ Tables.DATA_USAGE_STAT + "." + LR_TIMES_USED;
/** type: INTEGER */
public static final String USAGE_TYPE_INT = "usage_type";
@@ -903,6 +920,60 @@
}
}
+ /** Placeholder for the methods to build the "low-res" SQL expressions. */
+ @VisibleForTesting
+ interface LowRes {
+ /** To be replaced with a real column name. Only used within this interface. */
+ String TEMPLATE_PLACEHOLDER = "XX";
+
+ /**
+ * To be replaced with a constant in the expression.
+ * Only used within this interface.
+ */
+ String CONSTANT_PLACEHOLDER = "YY";
+
+ /** Only used within this interface. */
+ int TIMES_USED_GRANULARITY = 10;
+
+ /** Only used within this interface. */
+ int LAST_TIME_USED_GRANULARITY = 24 * 60 * 60;
+
+ /**
+ * Template to build the "low-res times used/contacted". Only used within this interface.
+ * The outermost cast is needed to tell SQLite that the result is of the integer type.
+ */
+ String TEMPLATE_TIMES_USED =
+ ("cast(ifnull((case when (XX) <= 0 then 0"
+ + " when (XX) < (YY) then 1"
+ + " else (cast((XX) as int) / (YY)) * (YY) end), 0) as int)")
+ .replaceAll(CONSTANT_PLACEHOLDER, String.valueOf(TIMES_USED_GRANULARITY));
+
+ /**
+ * Template to build the "low-res last time used/contacted".
+ * Only used within this interface.
+ * The outermost cast is needed to tell SQLite that the result is of the integer type.
+ */
+ String TEMPLATE_LAST_TIME_USED =
+ ("cast((cast((XX) as int) / (YY)) * (YY) as int)")
+ .replaceAll(CONSTANT_PLACEHOLDER, String.valueOf(LAST_TIME_USED_GRANULARITY));
+
+ /**
+ * Build the SQL expression for the "low-res times used/contacted" expression from the
+ * give column name.
+ */
+ static String getTimesUsedExpression(String column) {
+ return TEMPLATE_TIMES_USED.replaceAll(TEMPLATE_PLACEHOLDER, column);
+ }
+
+ /**
+ * Build the SQL expression for the "low-res last time used/contacted" expression from the
+ * give column name.
+ */
+ static String getLastTimeUsedExpression(String column) {
+ return TEMPLATE_LAST_TIME_USED.replaceAll(TEMPLATE_PLACEHOLDER, column);
+ }
+ }
+
private static final String TAG = "ContactsDatabaseHelper";
private static final String DATABASE_NAME = "contacts2.db";
@@ -1122,6 +1193,20 @@
AccountsColumns.DATA_SET + " TEXT" +
");");
+ // Note, there are two sets of the usage stat columns: LR_* and RAW_*.
+ // RAW_* contain the real values, which clients can't access. The column names start
+ // with a special prefix, which clients are prohibited from using in queries (including
+ // "where" of deletes/updates.)
+ // The LR_* columns have the original, public names. The views have the LR columns too,
+ // which contain the "low-res" numbers. The tables, though, do *not* have to have these
+ // columns, because we won't use them anyway. However, because old versions of the tables
+ // had those columns, and SQLite doesn't allow removing existing columns, meaning upgraded
+ // tables will have these LR_* columns anyway. So, in order to make a new database look
+ // the same as an upgraded database, we create the LR columns in a new database too.
+ // Otherwise, we would easily end up with writing SQLs that will run fine in a new DB
+ // but not in an upgraded database, and because all unit tests will run with a new database,
+ // we can't easily catch these sort of issues.
+
// One row per group of contacts corresponding to the same person
db.execSQL("CREATE TABLE " + Tables.CONTACTS + " (" +
BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
@@ -1130,8 +1215,13 @@
Contacts.PHOTO_FILE_ID + " INTEGER REFERENCES photo_files(_id)," +
Contacts.CUSTOM_RINGTONE + " TEXT," +
Contacts.SEND_TO_VOICEMAIL + " INTEGER NOT NULL DEFAULT 0," +
- Contacts.TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
- Contacts.LAST_TIME_CONTACTED + " INTEGER," +
+
+ Contacts.RAW_TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
+ Contacts.RAW_LAST_TIME_CONTACTED + " INTEGER," +
+
+ Contacts.LR_TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
+ Contacts.LR_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," +
@@ -1163,8 +1253,13 @@
RawContactsColumns.AGGREGATION_NEEDED + " INTEGER NOT NULL DEFAULT 1," +
RawContacts.CUSTOM_RINGTONE + " TEXT," +
RawContacts.SEND_TO_VOICEMAIL + " INTEGER NOT NULL DEFAULT 0," +
- RawContacts.TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
- RawContacts.LAST_TIME_CONTACTED + " INTEGER," +
+
+ RawContacts.RAW_TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
+ RawContacts.RAW_LAST_TIME_CONTACTED + " INTEGER," +
+
+ RawContacts.LR_TIMES_CONTACTED + " INTEGER NOT NULL DEFAULT 0," +
+ RawContacts.LR_LAST_TIME_CONTACTED + " INTEGER," +
+
RawContacts.STARRED + " INTEGER NOT NULL DEFAULT 0," +
RawContacts.PINNED + " INTEGER NOT NULL DEFAULT " + PinnedPositions.UNPINNED +
"," + RawContacts.DISPLAY_NAME_PRIMARY + " TEXT," +
@@ -1452,8 +1547,13 @@
DataUsageStatColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
DataUsageStatColumns.DATA_ID + " INTEGER NOT NULL, " +
DataUsageStatColumns.USAGE_TYPE_INT + " INTEGER NOT NULL DEFAULT 0, " +
- DataUsageStatColumns.TIMES_USED + " INTEGER NOT NULL DEFAULT 0, " +
- DataUsageStatColumns.LAST_TIME_USED + " INTEGER NOT NULL DEFAULT 0, " +
+
+ DataUsageStatColumns.RAW_TIMES_USED + " INTEGER NOT NULL DEFAULT 0, " +
+ DataUsageStatColumns.RAW_LAST_TIME_USED + " INTEGER NOT NULL DEFAULT 0, " +
+
+ DataUsageStatColumns.LR_TIMES_USED + " INTEGER NOT NULL DEFAULT 0, " +
+ DataUsageStatColumns.LR_LAST_TIME_USED + " INTEGER NOT NULL DEFAULT 0, " +
+
"FOREIGN KEY(" + DataUsageStatColumns.DATA_ID + ") REFERENCES "
+ Tables.DATA + "(" + Data._ID + ")" +
");");
@@ -1735,6 +1835,7 @@
db.execSQL("DROP VIEW IF EXISTS " + Views.RAW_ENTITIES + ";");
db.execSQL("DROP VIEW IF EXISTS " + Views.ENTITIES + ";");
db.execSQL("DROP VIEW IF EXISTS " + Views.DATA_USAGE_STAT + ";");
+ db.execSQL("DROP VIEW IF EXISTS " + Views.DATA_USAGE_LR + ";");
db.execSQL("DROP VIEW IF EXISTS " + Views.STREAM_ITEMS + ";");
db.execSQL("DROP VIEW IF EXISTS " + Views.METADATA_SYNC_STATE + ";");
db.execSQL("DROP VIEW IF EXISTS " + Views.METADATA_SYNC + ";");
@@ -1803,17 +1904,24 @@
String contactOptionColumns =
ContactsColumns.CONCRETE_CUSTOM_RINGTONE
- + " AS " + RawContacts.CUSTOM_RINGTONE + ","
+ + " AS " + Contacts.CUSTOM_RINGTONE + ","
+ ContactsColumns.CONCRETE_SEND_TO_VOICEMAIL
- + " AS " + RawContacts.SEND_TO_VOICEMAIL + ","
- + ContactsColumns.CONCRETE_LAST_TIME_CONTACTED
- + " AS " + RawContacts.LAST_TIME_CONTACTED + ","
- + ContactsColumns.CONCRETE_TIMES_CONTACTED
- + " AS " + RawContacts.TIMES_CONTACTED + ","
+ + " AS " + Contacts.SEND_TO_VOICEMAIL + ","
+
+ + ContactsColumns.CONCRETE_RAW_LAST_TIME_CONTACTED
+ + " AS " + Contacts.RAW_LAST_TIME_CONTACTED + ","
+ + ContactsColumns.CONCRETE_RAW_TIMES_CONTACTED
+ + " AS " + Contacts.RAW_TIMES_CONTACTED + ","
+
+ + LowRes.getLastTimeUsedExpression(ContactsColumns.CONCRETE_RAW_LAST_TIME_CONTACTED)
+ + " AS " + Contacts.LR_LAST_TIME_CONTACTED + ","
+ + LowRes.getTimesUsedExpression(ContactsColumns.CONCRETE_RAW_TIMES_CONTACTED)
+ + " AS " + Contacts.LR_TIMES_CONTACTED + ","
+
+ ContactsColumns.CONCRETE_STARRED
- + " AS " + RawContacts.STARRED + ","
+ + " AS " + Contacts.STARRED + ","
+ ContactsColumns.CONCRETE_PINNED
- + " AS " + RawContacts.PINNED;
+ + " AS " + Contacts.PINNED;
String contactNameColumns =
"name_raw_contact." + RawContacts.DISPLAY_NAME_SOURCE
@@ -1879,8 +1987,12 @@
String rawContactOptionColumns =
RawContacts.CUSTOM_RINGTONE + ","
+ RawContacts.SEND_TO_VOICEMAIL + ","
- + RawContacts.LAST_TIME_CONTACTED + ","
- + RawContacts.TIMES_CONTACTED + ","
+ + RawContacts.RAW_LAST_TIME_CONTACTED + ","
+ + LowRes.getLastTimeUsedExpression(RawContacts.RAW_LAST_TIME_CONTACTED)
+ + " AS " + RawContacts.LR_LAST_TIME_CONTACTED + ","
+ + RawContacts.RAW_TIMES_CONTACTED + ","
+ + LowRes.getTimesUsedExpression(RawContacts.RAW_TIMES_CONTACTED)
+ + " AS " + RawContacts.LR_TIMES_CONTACTED + ","
+ RawContacts.STARRED + ","
+ RawContacts.PINNED;
@@ -1917,16 +2029,23 @@
+ " AS " + Contacts.CUSTOM_RINGTONE + ", "
+ contactNameColumns + ", "
+ baseContactColumns + ", "
- + ContactsColumns.CONCRETE_LAST_TIME_CONTACTED
- + " AS " + Contacts.LAST_TIME_CONTACTED + ", "
+
+ + ContactsColumns.CONCRETE_RAW_LAST_TIME_CONTACTED
+ + " AS " + Contacts.RAW_LAST_TIME_CONTACTED + ", "
+ + LowRes.getLastTimeUsedExpression(ContactsColumns.CONCRETE_RAW_LAST_TIME_CONTACTED)
+ + " AS " + Contacts.LR_LAST_TIME_CONTACTED + ", "
+
+ ContactsColumns.CONCRETE_SEND_TO_VOICEMAIL
+ " 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;
+
+ + ContactsColumns.CONCRETE_RAW_TIMES_CONTACTED
+ + " AS " + Contacts.RAW_TIMES_CONTACTED + ", "
+ + LowRes.getTimesUsedExpression(ContactsColumns.CONCRETE_RAW_TIMES_CONTACTED)
+ + " AS " + Contacts.LR_TIMES_CONTACTED;
String contactsSelect = "SELECT "
+ ContactsColumns.CONCRETE_ID + " AS " + Contacts._ID + ","
@@ -2016,15 +2135,34 @@
db.execSQL("CREATE VIEW " + Views.ENTITIES + " AS "
+ entitiesSelect);
+ // Data usage view, with the low res columns, with no joins.
+ final String dataUsageViewSelect = "SELECT "
+ + DataUsageStatColumns._ID + ", "
+ + DataUsageStatColumns.DATA_ID + ", "
+ + DataUsageStatColumns.USAGE_TYPE_INT + ", "
+ + DataUsageStatColumns.RAW_TIMES_USED + ", "
+ + DataUsageStatColumns.RAW_LAST_TIME_USED + ","
+ + LowRes.getTimesUsedExpression(DataUsageStatColumns.RAW_TIMES_USED)
+ + " AS " + DataUsageStatColumns.LR_TIMES_USED + ","
+ + LowRes.getLastTimeUsedExpression(DataUsageStatColumns.RAW_LAST_TIME_USED)
+ + " AS " + DataUsageStatColumns.LR_LAST_TIME_USED
+ + " FROM " + Tables.DATA_USAGE_STAT;
+
+ // When the data_usage_stat table is needed with the low-res columns, use this, which is
+ // faster than the DATA_USAGE_STAT view since it doesn't involve joins.
+ db.execSQL("CREATE VIEW " + Views.DATA_USAGE_LR + " AS " + dataUsageViewSelect);
+
String dataUsageStatSelect = "SELECT "
+ DataUsageStatColumns.CONCRETE_ID + " AS " + DataUsageStatColumns._ID + ", "
+ DataUsageStatColumns.DATA_ID + ", "
+ RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + RawContacts.CONTACT_ID + ", "
+ MimetypesColumns.CONCRETE_MIMETYPE + " AS " + Data.MIMETYPE + ", "
+ DataUsageStatColumns.USAGE_TYPE_INT + ", "
- + DataUsageStatColumns.TIMES_USED + ", "
- + DataUsageStatColumns.LAST_TIME_USED
- + " FROM " + Tables.DATA_USAGE_STAT
+ + DataUsageStatColumns.RAW_TIMES_USED + ", "
+ + DataUsageStatColumns.RAW_LAST_TIME_USED + ", "
+ + DataUsageStatColumns.LR_TIMES_USED + ", "
+ + DataUsageStatColumns.LR_LAST_TIME_USED
+ + " FROM " + Views.DATA_USAGE_LR + " AS " + Tables.DATA_USAGE_STAT
+ " JOIN " + Tables.DATA + " ON ("
+ DataColumns.CONCRETE_ID + "=" + DataUsageStatColumns.CONCRETE_DATA_ID + ")"
+ " JOIN " + Tables.RAW_CONTACTS + " ON ("
@@ -2458,6 +2596,12 @@
oldVersion = 1200;
}
+ if (isUpgradeRequired(oldVersion, newVersion, 1201)) {
+ upgradeToVersion1201(db);
+ upgradeViewsAndTriggers = true;
+ oldVersion = 1201;
+ }
+
// We extracted "calls" and "voicemail_status" at this point, but we can't remove them here
// yet, until CallLogDatabaseHelper moves the data.
@@ -3201,6 +3345,35 @@
FastScrollingIndexCache.getInstance(mContext).invalidate();
}
+ private void upgradeToVersion1201(SQLiteDatabase db) {
+ db.execSQL("ALTER TABLE contacts ADD x_times_contacted INTEGER NOT NULL DEFAULT 0");
+ db.execSQL("ALTER TABLE contacts ADD x_last_time_contacted INTEGER");
+
+ db.execSQL("ALTER TABLE raw_contacts ADD x_times_contacted INTEGER NOT NULL DEFAULT 0");
+ db.execSQL("ALTER TABLE raw_contacts ADD x_last_time_contacted INTEGER");
+
+ db.execSQL("ALTER TABLE data_usage_stat ADD x_times_used INTEGER NOT NULL DEFAULT 0");
+ db.execSQL("ALTER TABLE data_usage_stat ADD x_last_time_used INTEGER NOT NULL DEFAULT 0");
+
+ db.execSQL("UPDATE contacts SET "
+ + "x_times_contacted = ifnull(times_contacted,0),"
+ + "x_last_time_contacted = ifnull(last_time_contacted,0),"
+ + "times_contacted = 0,"
+ + "last_time_contacted = 0");
+
+ db.execSQL("UPDATE raw_contacts SET "
+ + "x_times_contacted = ifnull(times_contacted,0),"
+ + "x_last_time_contacted = ifnull(last_time_contacted,0),"
+ + "times_contacted = 0,"
+ + "last_time_contacted = 0");
+
+ db.execSQL("UPDATE data_usage_stat SET "
+ + "x_times_used = ifnull(times_used,0),"
+ + "x_last_time_used = ifnull(last_time_used,0),"
+ + "times_used = 0,"
+ + "last_time_used = 0");
+ }
+
/**
* This method is only used in upgradeToVersion1101 method, and should not be used in other
* places now. Because data15 is not used to generate hash_id for photo, and the new generating
@@ -4816,12 +4989,19 @@
}
private void reportInvalidSql(String callerPackage, InvalidSqlException e) {
- final String message = String.format("%s caller=%s", e.getMessage(), callerPackage);
+ logWtf(String.format("%s caller=%s", e.getMessage(), callerPackage));
+ throw e; // STOPSHIP Don't throw for pre-O apps.
+ }
+
+ /**
+ * Calls WTF without crashing, so we can collect errors in the wild. During unit tests, it'll
+ * log only.
+ */
+ public void logWtf(String message) {
if (mIsTestInstance) {
Slog.w(TAG, "[Test mode, warning only] " + message);
} else {
Slog.wtfStack(TAG, message);
}
- throw e; // STOPSHIP Don't throw for pre-O apps.
}
}
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index c598d1c..ad87643 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -221,13 +221,13 @@
private static final String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS";
/* package */ static final String UPDATE_TIMES_CONTACTED_CONTACTS_TABLE =
- "UPDATE " + Tables.CONTACTS + " SET " + Contacts.TIMES_CONTACTED + "=" +
- " ifnull(" + Contacts.TIMES_CONTACTED + ",0)+1" +
+ "UPDATE " + Tables.CONTACTS + " SET " + Contacts.RAW_TIMES_CONTACTED + "=" +
+ " ifnull(" + Contacts.RAW_TIMES_CONTACTED + ",0)+1" +
" WHERE " + Contacts._ID + "=?";
/* package */ static final String UPDATE_TIMES_CONTACTED_RAWCONTACTS_TABLE =
- "UPDATE " + Tables.RAW_CONTACTS + " SET " + RawContacts.TIMES_CONTACTED + "=" +
- " ifnull(" + RawContacts.TIMES_CONTACTED + ",0)+1 " +
+ "UPDATE " + Tables.RAW_CONTACTS + " SET " + RawContacts.RAW_TIMES_CONTACTED + "=" +
+ " ifnull(" + RawContacts.RAW_TIMES_CONTACTED + ",0)+1 " +
" WHERE " + RawContacts.CONTACT_ID + "=?";
/* package */ static final String PHONEBOOK_COLLATOR_NAME = "PHONEBOOK";
@@ -314,7 +314,7 @@
public static final ProfileAwareUriMatcher sUriMatcher =
new ProfileAwareUriMatcher(UriMatcher.NO_MATCH);
- private static final String FREQUENT_ORDER_BY = DataUsageStatColumns.TIMES_USED + " DESC,"
+ private static final String FREQUENT_ORDER_BY = DataUsageStatColumns.RAW_TIMES_USED + " DESC,"
+ Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
public static final int CONTACTS = 1000;
@@ -611,20 +611,23 @@
// Contacts contacted within the last 30 days (in seconds)
private static final long LAST_TIME_USED_30_DAYS_SEC = 30L * 24 * 60 * 60;
- private static final String TIME_SINCE_LAST_USED_SEC =
- "(strftime('%s', 'now') - " + DataUsageStatColumns.LAST_TIME_USED + "/1000)";
+ private static final String RAW_TIME_SINCE_LAST_USED_SEC =
+ "(strftime('%s', 'now') - " + DataUsageStatColumns.RAW_LAST_TIME_USED + "/1000)";
+
+ private static final String LR_TIME_SINCE_LAST_USED_SEC =
+ "(strftime('%s', 'now') - " + DataUsageStatColumns.LR_LAST_TIME_USED + "/1000)";
private static final String SORT_BY_DATA_USAGE =
- "(CASE WHEN " + TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_3_DAYS_SEC +
+ "(CASE WHEN " + RAW_TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_3_DAYS_SEC +
" THEN 0 " +
- " WHEN " + TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_7_DAYS_SEC +
+ " WHEN " + RAW_TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_7_DAYS_SEC +
" THEN 1 " +
- " WHEN " + TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_14_DAYS_SEC +
+ " WHEN " + RAW_TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_14_DAYS_SEC +
" THEN 2 " +
- " WHEN " + TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_30_DAYS_SEC +
+ " WHEN " + RAW_TIME_SINCE_LAST_USED_SEC + " < " + LAST_TIME_USED_30_DAYS_SEC +
" THEN 3 " +
" ELSE 4 END), " +
- DataUsageStatColumns.TIMES_USED + " DESC";
+ DataUsageStatColumns.RAW_TIMES_USED + " DESC";
/*
* Sorting order for email address suggestions: first starred, then the rest.
@@ -677,7 +680,7 @@
.add(Contacts.DISPLAY_NAME_SOURCE)
.add(Contacts.IN_DEFAULT_DIRECTORY)
.add(Contacts.IN_VISIBLE_GROUP)
- .add(Contacts.LAST_TIME_CONTACTED)
+ .add(Contacts.LR_LAST_TIME_CONTACTED)
.add(Contacts.LOOKUP_KEY)
.add(Contacts.PHONETIC_NAME)
.add(Contacts.PHONETIC_NAME_STYLE)
@@ -694,7 +697,7 @@
.add(ContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE)
.add(Contacts.STARRED)
.add(Contacts.PINNED)
- .add(Contacts.TIMES_CONTACTED)
+ .add(Contacts.LR_TIMES_CONTACTED)
.add(Contacts.HAS_PHONE_NUMBER)
.add(Contacts.CONTACT_LAST_UPDATED_TIMESTAMP)
.build();
@@ -795,8 +798,8 @@
.build();
private static final ProjectionMap sDataUsageColumns = ProjectionMap.builder()
- .add(Data.TIMES_USED, Tables.DATA_USAGE_STAT + "." + Data.TIMES_USED)
- .add(Data.LAST_TIME_USED, Tables.DATA_USAGE_STAT + "." + Data.LAST_TIME_USED)
+ .add(Data.LR_TIMES_USED, Tables.DATA_USAGE_STAT + "." + Data.LR_TIMES_USED)
+ .add(Data.LR_LAST_TIME_USED, Tables.DATA_USAGE_STAT + "." + Data.LR_LAST_TIME_USED)
.build();
/** Contains just BaseColumns._COUNT */
@@ -823,16 +826,18 @@
/** Used for pushing starred contacts to the top of a times contacted list **/
private static final ProjectionMap sStrequentStarredProjectionMap = ProjectionMap.builder()
.addAll(sContactsProjectionMap)
- .add(DataUsageStatColumns.TIMES_USED, String.valueOf(Long.MAX_VALUE))
- .add(DataUsageStatColumns.LAST_TIME_USED, String.valueOf(Long.MAX_VALUE))
+ .add(DataUsageStatColumns.LR_TIMES_USED, String.valueOf(Long.MAX_VALUE))
+ .add(DataUsageStatColumns.LR_LAST_TIME_USED, String.valueOf(Long.MAX_VALUE))
.build();
private static final ProjectionMap sStrequentFrequentProjectionMap = ProjectionMap.builder()
.addAll(sContactsProjectionMap)
- .add(DataUsageStatColumns.TIMES_USED,
- "SUM(" + DataUsageStatColumns.CONCRETE_TIMES_USED + ")")
- .add(DataUsageStatColumns.LAST_TIME_USED,
- "MAX(" + DataUsageStatColumns.CONCRETE_LAST_TIME_USED + ")")
+ // Note this should ideally be "lowres(SUM)" rather than "SUM(lowres)", but we do it
+ // this way for performance reasons.
+ .add(DataUsageStatColumns.LR_TIMES_USED,
+ "SUM(" + DataUsageStatColumns.CONCRETE_LR_TIMES_USED + ")")
+ .add(DataUsageStatColumns.LR_LAST_TIME_USED,
+ "MAX(" + DataUsageStatColumns.CONCRETE_LR_LAST_TIME_USED + ")")
.build();
/**
@@ -844,8 +849,8 @@
private static final ProjectionMap sStrequentPhoneOnlyProjectionMap
= ProjectionMap.builder()
.addAll(sContactsProjectionMap)
- .add(DataUsageStatColumns.TIMES_USED, DataUsageStatColumns.CONCRETE_TIMES_USED)
- .add(DataUsageStatColumns.LAST_TIME_USED, DataUsageStatColumns.CONCRETE_LAST_TIME_USED)
+ .add(DataUsageStatColumns.LR_TIMES_USED)
+ .add(DataUsageStatColumns.LR_LAST_TIME_USED)
.add(Phone.NUMBER)
.add(Phone.TYPE)
.add(Phone.LABEL)
@@ -877,8 +882,8 @@
.add(RawContactsColumns.PHONEBOOK_BUCKET_PRIMARY)
.add(RawContactsColumns.PHONEBOOK_LABEL_ALTERNATIVE)
.add(RawContactsColumns.PHONEBOOK_BUCKET_ALTERNATIVE)
- .add(RawContacts.TIMES_CONTACTED)
- .add(RawContacts.LAST_TIME_CONTACTED)
+ .add(RawContacts.LR_TIMES_CONTACTED)
+ .add(RawContacts.LR_LAST_TIME_CONTACTED)
.add(RawContacts.CUSTOM_RINGTONE)
.add(RawContacts.SEND_TO_VOICEMAIL)
.add(RawContacts.STARRED)
@@ -985,8 +990,8 @@
.add(PhoneLookup.PHONETIC_NAME_STYLE, "contacts_view." + Contacts.PHONETIC_NAME_STYLE)
.add(PhoneLookup.SORT_KEY_PRIMARY, "contacts_view." + Contacts.SORT_KEY_PRIMARY)
.add(PhoneLookup.SORT_KEY_ALTERNATIVE, "contacts_view." + Contacts.SORT_KEY_ALTERNATIVE)
- .add(PhoneLookup.LAST_TIME_CONTACTED, "contacts_view." + Contacts.LAST_TIME_CONTACTED)
- .add(PhoneLookup.TIMES_CONTACTED, "contacts_view." + Contacts.TIMES_CONTACTED)
+ .add(PhoneLookup.LR_LAST_TIME_CONTACTED, "contacts_view." + Contacts.LR_LAST_TIME_CONTACTED)
+ .add(PhoneLookup.LR_TIMES_CONTACTED, "contacts_view." + Contacts.LR_TIMES_CONTACTED)
.add(PhoneLookup.STARRED, "contacts_view." + Contacts.STARRED)
.add(PhoneLookup.IN_DEFAULT_DIRECTORY, "contacts_view." + Contacts.IN_DEFAULT_DIRECTORY)
.add(PhoneLookup.IN_VISIBLE_GROUP, "contacts_view." + Contacts.IN_VISIBLE_GROUP)
@@ -2879,6 +2884,8 @@
private long insertRawContact(
Uri uri, ContentValues inputValues, boolean callerIsSyncAdapter) {
+ inputValues = fixUpUsageColumnsForEdit(inputValues);
+
// Create a shallow copy and initialize the contact ID to null.
final ContentValues values = new ContentValues(inputValues);
values.putNull(RawContacts.CONTACT_ID);
@@ -4001,12 +4008,12 @@
private int deleteDataUsage() {
final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
db.execSQL("UPDATE " + Tables.RAW_CONTACTS + " SET " +
- Contacts.TIMES_CONTACTED + "=0," +
- Contacts.LAST_TIME_CONTACTED + "=NULL");
+ Contacts.RAW_TIMES_CONTACTED + "=0," +
+ Contacts.RAW_LAST_TIME_CONTACTED + "=NULL");
db.execSQL("UPDATE " + Tables.CONTACTS + " SET " +
- Contacts.TIMES_CONTACTED + "=0," +
- Contacts.LAST_TIME_CONTACTED + "=NULL");
+ Contacts.RAW_TIMES_CONTACTED + "=0," +
+ Contacts.RAW_LAST_TIME_CONTACTED + "=NULL");
db.delete(Tables.DATA_USAGE_STAT, null, null);
return 1;
@@ -4529,11 +4536,42 @@
return count;
}
+ /**
+ * Used for insert/update raw_contacts/contacts to adjust TIMES_CONTACTED and
+ * LAST_TIME_CONTACTED.
+ */
+ private ContentValues fixUpUsageColumnsForEdit(ContentValues cv) {
+ if (!cv.containsKey(Contacts.LR_LAST_TIME_CONTACTED)
+ && !cv.containsKey(Contacts.LR_TIMES_CONTACTED)) {
+ return cv;
+ }
+ final ContentValues ret = new ContentValues(cv);
+
+ ContactsDatabaseHelper.copyLongValue(
+ ret, Contacts.RAW_LAST_TIME_CONTACTED,
+ ret, Contacts.LR_LAST_TIME_CONTACTED);
+ if (ret.containsKey(Contacts.LR_TIMES_CONTACTED)) {
+ getDatabaseHelper().logWtf(
+ "Column '" + Contacts.LR_TIMES_CONTACTED + "' can no longer be modified"
+ + "directly.");
+ }
+
+ ret.remove(Contacts.LR_LAST_TIME_CONTACTED);
+ ret.remove(Contacts.LR_TIMES_CONTACTED);
+ return ret;
+ }
+
private int updateRawContact(SQLiteDatabase db, long rawContactId, ContentValues values,
boolean callerIsSyncAdapter, boolean callerIsMetadataSyncAdapter) {
final String selection = RawContactsColumns.CONCRETE_ID + " = ?";
mSelectionArgs1[0] = Long.toString(rawContactId);
+ values = fixUpUsageColumnsForEdit(values);
+
+ if (values.size() == 0) {
+ return 0; // Nothing to update; bail out.
+ }
+
final ContactsDatabaseHelper dbHelper = mDbHelper.get();
final boolean requestUndoDelete = flagIsClear(values, RawContacts.DELETED);
@@ -4768,6 +4806,8 @@
private int updateContactOptions(
SQLiteDatabase db, long contactId, ContentValues inputValues, boolean callerIsSyncAdapter) {
+ inputValues = fixUpUsageColumnsForEdit(inputValues);
+
final ContentValues values = new ContentValues();
ContactsDatabaseHelper.copyStringValue(
values, RawContacts.CUSTOM_RINGTONE,
@@ -4776,11 +4816,11 @@
values, RawContacts.SEND_TO_VOICEMAIL,
inputValues, Contacts.SEND_TO_VOICEMAIL);
ContactsDatabaseHelper.copyLongValue(
- values, RawContacts.LAST_TIME_CONTACTED,
- inputValues, Contacts.LAST_TIME_CONTACTED);
+ values, RawContacts.RAW_LAST_TIME_CONTACTED,
+ inputValues, Contacts.RAW_LAST_TIME_CONTACTED);
ContactsDatabaseHelper.copyLongValue(
- values, RawContacts.TIMES_CONTACTED,
- inputValues, Contacts.TIMES_CONTACTED);
+ values, RawContacts.RAW_TIMES_CONTACTED,
+ inputValues, Contacts.RAW_TIMES_CONTACTED);
ContactsDatabaseHelper.copyLongValue(
values, RawContacts.STARRED,
inputValues, Contacts.STARRED);
@@ -4838,11 +4878,11 @@
values, RawContacts.SEND_TO_VOICEMAIL,
inputValues, Contacts.SEND_TO_VOICEMAIL);
ContactsDatabaseHelper.copyLongValue(
- values, RawContacts.LAST_TIME_CONTACTED,
- inputValues, Contacts.LAST_TIME_CONTACTED);
+ values, RawContacts.RAW_LAST_TIME_CONTACTED,
+ inputValues, Contacts.RAW_LAST_TIME_CONTACTED);
ContactsDatabaseHelper.copyLongValue(
- values, RawContacts.TIMES_CONTACTED,
- inputValues, Contacts.TIMES_CONTACTED);
+ values, RawContacts.RAW_TIMES_CONTACTED,
+ inputValues, Contacts.RAW_TIMES_CONTACTED);
ContactsDatabaseHelper.copyLongValue(
values, RawContacts.STARRED,
inputValues, Contacts.STARRED);
@@ -4856,8 +4896,8 @@
int rslt = db.update(Tables.CONTACTS, values, Contacts._ID + "=?",
mSelectionArgs1);
- if (inputValues.containsKey(Contacts.LAST_TIME_CONTACTED) &&
- !inputValues.containsKey(Contacts.TIMES_CONTACTED)) {
+ if (inputValues.containsKey(Contacts.RAW_LAST_TIME_CONTACTED) &&
+ !inputValues.containsKey(Contacts.RAW_TIMES_CONTACTED)) {
db.execSQL(UPDATE_TIMES_CONTACTED_CONTACTS_TABLE, mSelectionArgs1);
db.execSQL(UPDATE_TIMES_CONTACTED_RAWCONTACTS_TABLE, mSelectionArgs1);
}
@@ -5098,8 +5138,8 @@
ContentValues usageStatsValues = new ContentValues();
usageStatsValues.put(DataUsageStatColumns.DATA_ID, dataId);
usageStatsValues.put(DataUsageStatColumns.USAGE_TYPE_INT, typeInt);
- usageStatsValues.put(DataUsageStatColumns.LAST_TIME_USED, lastTimeUsed);
- usageStatsValues.put(DataUsageStatColumns.TIMES_USED, timesUsed);
+ usageStatsValues.put(DataUsageStatColumns.RAW_LAST_TIME_USED, lastTimeUsed);
+ usageStatsValues.put(DataUsageStatColumns.RAW_TIMES_USED, timesUsed);
updateDataUsageStats(db, usageStatsValues);
}
}
@@ -5980,8 +6020,8 @@
if (projection != null) {
subProjection = new String[projection.length + 2];
System.arraycopy(projection, 0, subProjection, 0, projection.length);
- subProjection[projection.length + 0] = DataUsageStatColumns.TIMES_USED;
- subProjection[projection.length + 1] = DataUsageStatColumns.LAST_TIME_USED;
+ subProjection[projection.length + 0] = DataUsageStatColumns.LR_TIMES_USED;
+ subProjection[projection.length + 1] = DataUsageStatColumns.LR_LAST_TIME_USED;
}
// String that will store the query for starred contacts. For phone only queries,
@@ -6004,7 +6044,8 @@
// it is included in the list of strequent numbers.
tableBuilder.append("(SELECT * FROM " + Views.DATA + " WHERE "
+ Contacts.STARRED + "=1)" + " AS " + Tables.DATA
- + " LEFT OUTER JOIN " + Tables.DATA_USAGE_STAT
+ + " LEFT OUTER JOIN " + Views.DATA_USAGE_LR
+ + " AS " + Tables.DATA_USAGE_STAT
+ " ON (" + DataUsageStatColumns.CONCRETE_DATA_ID + "="
+ DataColumns.CONCRETE_ID + " AND "
+ DataUsageStatColumns.CONCRETE_USAGE_TYPE + "="
@@ -6037,7 +6078,7 @@
// data rows (almost always it should be), and we don't want any phone
// numbers not used by the user. This way sqlite is able to drop a number of
// rows in view_data in the early stage of data lookup.
- tableBuilder.append(Tables.DATA_USAGE_STAT
+ tableBuilder.append(Views.DATA_USAGE_LR + " AS " + Tables.DATA_USAGE_STAT
+ " INNER JOIN " + Views.DATA + " " + Tables.DATA
+ " ON (" + DataUsageStatColumns.CONCRETE_DATA_ID + "="
+ DataColumns.CONCRETE_ID + " AND "
@@ -6090,7 +6131,7 @@
// Phone numbers that were used more than 30 days ago are dropped from frequents
final String frequentQuery = "SELECT * FROM (" + frequentInnerQuery + ") WHERE " +
- TIME_SINCE_LAST_USED_SEC + "<" + LAST_TIME_USED_30_DAYS_SEC;
+ LR_TIME_SINCE_LAST_USED_SEC + "<" + LAST_TIME_USED_30_DAYS_SEC;
final String starredQuery = "SELECT * FROM (" + starredInnerQuery + ")";
// Put them together
@@ -8010,7 +8051,7 @@
if (includeDataUsageStat) {
sb.append(" ON (" +
DbQueryUtils.concatenateClauses(
- DataUsageStatColumns.CONCRETE_TIMES_USED + " > 0",
+ DataUsageStatColumns.CONCRETE_RAW_TIMES_USED + " > 0",
RawContacts.CONTACT_ID + "=" + Views.CONTACTS + "." + Contacts._ID) +
")");
}
@@ -8397,7 +8438,8 @@
private void appendDataUsageStatJoin(StringBuilder sb, int usageType, String dataIdColumn) {
if (usageType != USAGE_TYPE_ALL) {
- sb.append(" LEFT OUTER JOIN " + Tables.DATA_USAGE_STAT +
+ sb.append(" LEFT OUTER JOIN " + Views.DATA_USAGE_LR +
+ " as " + Tables.DATA_USAGE_STAT +
" ON (" + DataUsageStatColumns.CONCRETE_DATA_ID + "=");
sb.append(dataIdColumn);
sb.append(" AND " + DataUsageStatColumns.CONCRETE_USAGE_TYPE + "=");
@@ -8407,13 +8449,21 @@
sb.append(
" LEFT OUTER JOIN " +
"(SELECT " +
- DataUsageStatColumns.CONCRETE_DATA_ID + " as STAT_DATA_ID, " +
- "SUM(" + DataUsageStatColumns.CONCRETE_TIMES_USED +
- ") as " + DataUsageStatColumns.TIMES_USED + ", " +
- "MAX(" + DataUsageStatColumns.CONCRETE_LAST_TIME_USED +
- ") as " + DataUsageStatColumns.LAST_TIME_USED +
- " FROM " + Tables.DATA_USAGE_STAT + " GROUP BY " +
- DataUsageStatColumns.CONCRETE_DATA_ID + ") as " + Tables.DATA_USAGE_STAT
+ DataUsageStatColumns.DATA_ID + " as STAT_DATA_ID," +
+ " SUM(ifnull(" + DataUsageStatColumns.RAW_TIMES_USED +
+ ",0)) as " + DataUsageStatColumns.RAW_TIMES_USED + ", " +
+ " MAX(ifnull(" + DataUsageStatColumns.RAW_LAST_TIME_USED +
+ ",0)) as " + DataUsageStatColumns.RAW_LAST_TIME_USED + "," +
+
+ // Note this is not ideal -- we should use "lowres(sum(LR_TIMES_USED))"
+ // here, but for performance reasons we just do it simple.
+ " SUM(ifnull(" + DataUsageStatColumns.LR_TIMES_USED +
+ ",0)) as " + DataUsageStatColumns.LR_TIMES_USED + ", " +
+
+ " MAX(ifnull(" + DataUsageStatColumns.LR_LAST_TIME_USED +
+ ",0)) as " + DataUsageStatColumns.LR_LAST_TIME_USED +
+ " FROM " + Views.DATA_USAGE_LR + " GROUP BY " +
+ DataUsageStatColumns.DATA_ID + ") as " + Tables.DATA_USAGE_STAT
);
sb.append(" ON (STAT_DATA_ID=");
sb.append(dataIdColumn);
@@ -9831,15 +9881,15 @@
final String rids = TextUtils.join(",", rawContactIds);
db.execSQL("UPDATE " + Tables.RAW_CONTACTS +
- " SET " + RawContacts.LAST_TIME_CONTACTED + "=?" +
- "," + RawContacts.TIMES_CONTACTED + "=" +
- "ifnull(" + RawContacts.TIMES_CONTACTED + ",0) + 1" +
+ " SET " + RawContacts.RAW_LAST_TIME_CONTACTED + "=?" +
+ "," + RawContacts.RAW_TIMES_CONTACTED + "=" +
+ "ifnull(" + RawContacts.RAW_TIMES_CONTACTED + ",0) + 1" +
" WHERE " + RawContacts._ID + " IN (" + rids + ")"
, mSelectionArgs1);
db.execSQL("UPDATE " + Tables.CONTACTS +
- " SET " + Contacts.LAST_TIME_CONTACTED + "=?1" +
- "," + Contacts.TIMES_CONTACTED + "=" +
- "ifnull(" + Contacts.TIMES_CONTACTED + ",0) + 1" +
+ " SET " + Contacts.RAW_LAST_TIME_CONTACTED + "=?1" +
+ "," + Contacts.RAW_TIMES_CONTACTED + "=" +
+ "ifnull(" + Contacts.RAW_TIMES_CONTACTED + ",0) + 1" +
"," + Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + "=?1" +
" WHERE " + Contacts._ID + " IN (SELECT " + RawContacts.CONTACT_ID +
" FROM " + Tables.RAW_CONTACTS +
@@ -9886,9 +9936,9 @@
mSelectionArgs2[1] = String.valueOf(id);
db.execSQL("UPDATE " + Tables.DATA_USAGE_STAT +
- " SET " + DataUsageStatColumns.TIMES_USED + "=" +
- "ifnull(" + DataUsageStatColumns.TIMES_USED +",0)+1" +
- "," + DataUsageStatColumns.LAST_TIME_USED + "=?" +
+ " SET " + DataUsageStatColumns.RAW_TIMES_USED + "=" +
+ "ifnull(" + DataUsageStatColumns.RAW_TIMES_USED +",0)+1" +
+ "," + DataUsageStatColumns.RAW_LAST_TIME_USED + "=?" +
" WHERE " + DataUsageStatColumns._ID + "=?",
mSelectionArgs2);
} else {
@@ -9899,8 +9949,8 @@
db.execSQL("INSERT INTO " + Tables.DATA_USAGE_STAT +
"(" + DataUsageStatColumns.DATA_ID +
"," + DataUsageStatColumns.USAGE_TYPE_INT +
- "," + DataUsageStatColumns.TIMES_USED +
- "," + DataUsageStatColumns.LAST_TIME_USED +
+ "," + DataUsageStatColumns.RAW_TIMES_USED +
+ "," + DataUsageStatColumns.RAW_LAST_TIME_USED +
") VALUES (?,?,?,?)",
mSelectionArgs4);
}
@@ -9913,14 +9963,14 @@
}
/**
- * Update {@link Tables#DATA_USAGE_STAT}.
+ * Directly update {@link Tables#DATA_USAGE_STAT}; used for metadata sync.
* Update or insert usageType, lastTimeUsed, and timesUsed for specific dataId.
*/
private void updateDataUsageStats(SQLiteDatabase db, ContentValues values) {
final String dataId = values.getAsString(DataUsageStatColumns.DATA_ID);
final String type = values.getAsString(DataUsageStatColumns.USAGE_TYPE_INT);
- final String lastTimeUsed = values.getAsString(DataUsageStatColumns.LAST_TIME_USED);
- final String timesUsed = values.getAsString(DataUsageStatColumns.TIMES_USED);
+ final String lastTimeUsed = values.getAsString(DataUsageStatColumns.RAW_LAST_TIME_USED);
+ final String timesUsed = values.getAsString(DataUsageStatColumns.RAW_TIMES_USED);
mSelectionArgs2[0] = dataId;
mSelectionArgs2[1] = type;
@@ -9936,8 +9986,8 @@
mSelectionArgs3[1] = timesUsed;
mSelectionArgs3[2] = String.valueOf(id);
db.execSQL("UPDATE " + Tables.DATA_USAGE_STAT +
- " SET " + DataUsageStatColumns.LAST_TIME_USED + "=?" +
- "," + DataUsageStatColumns.TIMES_USED + "=?" +
+ " SET " + DataUsageStatColumns.RAW_LAST_TIME_USED + "=?" +
+ "," + DataUsageStatColumns.RAW_TIMES_USED + "=?" +
" WHERE " + DataUsageStatColumns._ID + "=?",
mSelectionArgs3);
} else {
@@ -9948,8 +9998,8 @@
db.execSQL("INSERT INTO " + Tables.DATA_USAGE_STAT +
"(" + DataUsageStatColumns.DATA_ID +
"," + DataUsageStatColumns.USAGE_TYPE_INT +
- "," + DataUsageStatColumns.TIMES_USED +
- "," + DataUsageStatColumns.LAST_TIME_USED +
+ "," + DataUsageStatColumns.RAW_TIMES_USED +
+ "," + DataUsageStatColumns.RAW_LAST_TIME_USED +
") VALUES (?,?,?,?)",
mSelectionArgs4);
}
@@ -10186,4 +10236,9 @@
public ContactsDatabaseHelper getContactsDatabaseHelperForTest() {
return mContactsHelper;
}
+
+ @VisibleForTesting
+ public ProfileProvider getProfileProviderForTest() {
+ return mProfileProvider;
+ }
}
diff --git a/src/com/android/providers/contacts/GlobalSearchSupport.java b/src/com/android/providers/contacts/GlobalSearchSupport.java
index 20307d4..6678b95 100644
--- a/src/com/android/providers/contacts/GlobalSearchSupport.java
+++ b/src/com/android/providers/contacts/GlobalSearchSupport.java
@@ -232,7 +232,7 @@
+ Contacts.PHOTO_THUMBNAIL_URI + ", "
+ Contacts.DISPLAY_NAME + ", "
+ PRESENCE_SQL + " AS " + Contacts.CONTACT_PRESENCE + ", "
- + Contacts.LAST_TIME_CONTACTED);
+ + Contacts.LR_LAST_TIME_CONTACTED);
if (haveFilter) {
sb.append(", " + SearchSnippets.SNIPPET);
}
diff --git a/src/com/android/providers/contacts/LegacyApiSupport.java b/src/com/android/providers/contacts/LegacyApiSupport.java
index 961d2a4..741639a 100644
--- a/src/com/android/providers/contacts/LegacyApiSupport.java
+++ b/src/com/android/providers/contacts/LegacyApiSupport.java
@@ -181,15 +181,6 @@
+ " ELSE " + Tables.DATA + "." + Email.DATA
+ " END)";
- private static final String CONTACTS_UPDATE_LASTTIMECONTACTED =
- "UPDATE " + Tables.CONTACTS +
- " SET " + Contacts.LAST_TIME_CONTACTED + "=? " +
- "WHERE " + Contacts._ID + "=?";
- private static final String RAWCONTACTS_UPDATE_LASTTIMECONTACTED =
- "UPDATE " + Tables.RAW_CONTACTS + " SET "
- + RawContacts.LAST_TIME_CONTACTED + "=? WHERE "
- + RawContacts._ID + "=?";
-
private String[] mSelectionArgs1 = new String[1];
private String[] mSelectionArgs2 = new String[2];
@@ -546,10 +537,12 @@
+ " AS " + People.NOTES + ", " +
AccountsColumns.CONCRETE_ACCOUNT_NAME + ", " +
AccountsColumns.CONCRETE_ACCOUNT_TYPE + ", " +
- Tables.RAW_CONTACTS + "." + RawContacts.TIMES_CONTACTED
- + " AS " + People.TIMES_CONTACTED + ", " +
- Tables.RAW_CONTACTS + "." + RawContacts.LAST_TIME_CONTACTED
- + " AS " + People.LAST_TIME_CONTACTED + ", " +
+
+ // We no longer return even low-res values from CP1.
+ // Note if we just use the value 0 below, certain seletion wouldn't work.
+ "cast(0 as int) AS " + People.TIMES_CONTACTED + ", " +
+ "cast(0 as int) AS " + People.LAST_TIME_CONTACTED + ", " +
+
Tables.RAW_CONTACTS + "." + RawContacts.CUSTOM_RINGTONE
+ " AS " + People.CUSTOM_RINGTONE + ", " +
Tables.RAW_CONTACTS + "." + RawContacts.SEND_TO_VOICEMAIL
@@ -926,7 +919,7 @@
int count = 0;
switch(match) {
case PEOPLE_UPDATE_CONTACT_TIME: {
- count = updateContactTime(uri, values);
+ count = 0; // No longer supported.
break;
}
@@ -1055,11 +1048,6 @@
}
}
- if (values.containsKey(People.LAST_TIME_CONTACTED) &&
- !values.containsKey(People.TIMES_CONTACTED)) {
- updateContactTime(rawContactId, values);
- }
-
return count;
}
@@ -1121,35 +1109,6 @@
Groups._ID + "=" + groupId, null);
}
- private int updateContactTime(Uri uri, ContentValues values) {
- long rawContactId = Long.parseLong(uri.getPathSegments().get(1));
- updateContactTime(rawContactId, values);
- return 1;
- }
-
- private void updateContactTime(long rawContactId, ContentValues values) {
- final Long storedTimeContacted = values.getAsLong(People.LAST_TIME_CONTACTED);
- final long lastTimeContacted = storedTimeContacted != null ?
- storedTimeContacted : System.currentTimeMillis();
-
- // TODO check sanctions
- long contactId = mDbHelper.getContactId(rawContactId);
- SQLiteDatabase mDb = mDbHelper.getWritableDatabase();
- mSelectionArgs2[0] = String.valueOf(lastTimeContacted);
- if (contactId != 0) {
- mSelectionArgs2[1] = String.valueOf(contactId);
- mDb.execSQL(CONTACTS_UPDATE_LASTTIMECONTACTED, mSelectionArgs2);
- // increment times_contacted column
- mSelectionArgs1[0] = String.valueOf(contactId);
- mDb.execSQL(ContactsProvider2.UPDATE_TIMES_CONTACTED_CONTACTS_TABLE, mSelectionArgs1);
- }
- mSelectionArgs2[1] = String.valueOf(rawContactId);
- mDb.execSQL(RAWCONTACTS_UPDATE_LASTTIMECONTACTED, mSelectionArgs2);
- // increment times_contacted column
- mSelectionArgs1[0] = String.valueOf(contactId);
- mDb.execSQL(ContactsProvider2.UPDATE_TIMES_CONTACTED_RAWCONTACTS_TABLE, mSelectionArgs1);
- }
-
private int updatePhoto(long rawContactId, ContentValues values) {
// TODO check sanctions
@@ -1337,10 +1296,12 @@
values, People.CUSTOM_RINGTONE);
ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.SEND_TO_VOICEMAIL,
values, People.SEND_TO_VOICEMAIL);
- ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.LAST_TIME_CONTACTED,
- values, People.LAST_TIME_CONTACTED);
- ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.TIMES_CONTACTED,
- values, People.TIMES_CONTACTED);
+
+ // We no longer support the following fields in CP1.
+ // ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.LAST_TIME_CONTACTED,
+ // values, People.LAST_TIME_CONTACTED);
+ // ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.TIMES_CONTACTED,
+ // values, People.TIMES_CONTACTED);
ContactsDatabaseHelper.copyLongValue(mValues, RawContacts.STARRED,
values, People.STARRED);
if (mAccount != null) {
diff --git a/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java b/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java
index 1501138..2a36675 100644
--- a/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java
+++ b/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java
@@ -1263,8 +1263,8 @@
+ RawContacts.SOURCE_ID + ","
+ RawContacts.CUSTOM_RINGTONE + ","
+ RawContacts.SEND_TO_VOICEMAIL + ","
- + RawContacts.LAST_TIME_CONTACTED + ","
- + RawContacts.TIMES_CONTACTED + ","
+ + RawContacts.RAW_LAST_TIME_CONTACTED + ","
+ + RawContacts.RAW_TIMES_CONTACTED + ","
+ RawContacts.STARRED + ","
+ RawContacts.PINNED + ","
+ DataColumns.CONCRETE_ID + ","
@@ -1299,8 +1299,8 @@
int SOURCE_ID = 6;
int CUSTOM_RINGTONE = 7;
int SEND_TO_VOICEMAIL = 8;
- int LAST_TIME_CONTACTED = 9;
- int TIMES_CONTACTED = 10;
+ int RAW_LAST_TIME_CONTACTED = 9;
+ int RAW_TIMES_CONTACTED = 10;
int STARRED = 11;
int PINNED = 12;
int DATA_ID = 13;
@@ -1319,8 +1319,8 @@
+ Contacts.PHOTO_FILE_ID + "=?, "
+ Contacts.SEND_TO_VOICEMAIL + "=?, "
+ Contacts.CUSTOM_RINGTONE + "=?, "
- + Contacts.LAST_TIME_CONTACTED + "=?, "
- + Contacts.TIMES_CONTACTED + "=?, "
+ + Contacts.RAW_LAST_TIME_CONTACTED + "=?, "
+ + Contacts.RAW_TIMES_CONTACTED + "=?, "
+ Contacts.STARRED + "=?, "
+ Contacts.PINNED + "=?, "
+ Contacts.HAS_PHONE_NUMBER + "=?, "
@@ -1335,8 +1335,8 @@
+ Contacts.PHOTO_FILE_ID + ", "
+ Contacts.SEND_TO_VOICEMAIL + ", "
+ Contacts.CUSTOM_RINGTONE + ", "
- + Contacts.LAST_TIME_CONTACTED + ", "
- + Contacts.TIMES_CONTACTED + ", "
+ + Contacts.RAW_LAST_TIME_CONTACTED + ", "
+ + Contacts.RAW_TIMES_CONTACTED + ", "
+ Contacts.STARRED + ", "
+ Contacts.PINNED + ", "
+ Contacts.HAS_PHONE_NUMBER + ", "
@@ -1350,8 +1350,8 @@
int PHOTO_FILE_ID = 3;
int SEND_TO_VOICEMAIL = 4;
int CUSTOM_RINGTONE = 5;
- int LAST_TIME_CONTACTED = 6;
- int TIMES_CONTACTED = 7;
+ int RAW_LAST_TIME_CONTACTED = 6;
+ int RAW_TIMES_CONTACTED = 7;
int STARRED = 8;
int PINNED = 9;
int HAS_PHONE_NUMBER = 10;
@@ -1439,12 +1439,12 @@
contactCustomRingtone = c.getString(RawContactsQuery.CUSTOM_RINGTONE);
}
- long lastTimeContacted = c.getLong(RawContactsQuery.LAST_TIME_CONTACTED);
+ long lastTimeContacted = c.getLong(RawContactsQuery.RAW_LAST_TIME_CONTACTED);
if (lastTimeContacted > contactLastTimeContacted) {
contactLastTimeContacted = lastTimeContacted;
}
- int timesContacted = c.getInt(RawContactsQuery.TIMES_CONTACTED);
+ int timesContacted = c.getInt(RawContactsQuery.RAW_TIMES_CONTACTED);
if (timesContacted > contactTimesContacted) {
contactTimesContacted = timesContacted;
}
@@ -1523,9 +1523,9 @@
totalRowCount == contactSendToVoicemail ? 1 : 0);
DatabaseUtils.bindObjectToProgram(statement, ContactReplaceSqlStatement.CUSTOM_RINGTONE,
contactCustomRingtone);
- statement.bindLong(ContactReplaceSqlStatement.LAST_TIME_CONTACTED,
+ statement.bindLong(ContactReplaceSqlStatement.RAW_LAST_TIME_CONTACTED,
contactLastTimeContacted);
- statement.bindLong(ContactReplaceSqlStatement.TIMES_CONTACTED,
+ statement.bindLong(ContactReplaceSqlStatement.RAW_TIMES_CONTACTED,
contactTimesContacted);
statement.bindLong(ContactReplaceSqlStatement.STARRED,
contactStarred);
diff --git a/src/com/android/providers/contacts/sqlite/SqlChecker.java b/src/com/android/providers/contacts/sqlite/SqlChecker.java
index 6404220..2db1f47 100644
--- a/src/com/android/providers/contacts/sqlite/SqlChecker.java
+++ b/src/com/android/providers/contacts/sqlite/SqlChecker.java
@@ -33,6 +33,8 @@
public class SqlChecker {
private static final String TAG = "SqlChecker";
+ private static final String PRIVATE_PREFIX = "x_"; // MUST BE LOWERCASE.
+
private static final boolean VERBOSE_LOGGING = AbstractContactsProvider.VERBOSE_LOGGING;
private final ArraySet<String> mInvalidTokens;
@@ -47,7 +49,7 @@
mInvalidTokens.add(invalidTokens.get(i).toLowerCase());
}
if (VERBOSE_LOGGING) {
- Log.d(TAG, "Intialized with invalid tokens: " + invalidTokens);
+ Log.d(TAG, "Initialized with invalid tokens: " + invalidTokens);
}
}
@@ -82,7 +84,8 @@
}
private void throwIfContainsToken(String token, String sql) {
- if (mInvalidTokens.contains(token.toLowerCase())) {
+ final String lower = token.toLowerCase();
+ if (mInvalidTokens.contains(lower) || lower.startsWith(PRIVATE_PREFIX)) {
throw genException("Detected disallowed token: " + token, sql);
}
}
diff --git a/tests/assets/upgradeTest/pre_upgrade1201.sql b/tests/assets/upgradeTest/pre_upgrade1201.sql
new file mode 100644
index 0000000..6a284ea
--- /dev/null
+++ b/tests/assets/upgradeTest/pre_upgrade1201.sql
@@ -0,0 +1,27 @@
+DELETE FROM accounts;
+DELETE FROM contacts;
+DELETE FROM raw_contacts;
+DELETE FROM data;
+DELETE FROM data_usage_stat;
+
+--CREATE TABLE accounts (_id INTEGER PRIMARY KEY AUTOINCREMENT,account_name TEXT, account_type TEXT, data_set TEXT);
+
+INSERT INTO "accounts" VALUES(1,NULL,NULL,NULL);
+
+--CREATE TABLE contacts (_id INTEGER PRIMARY KEY AUTOINCREMENT,name_raw_contact_id INTEGER REFERENCES raw_contacts(_id),photo_id INTEGER REFERENCES data(_id),photo_file_id INTEGER REFERENCES photo_files(_id),custom_ringtone TEXT,send_to_voicemail INTEGER NOT NULL DEFAULT 0,times_contacted INTEGER NOT NULL DEFAULT 0,last_time_contacted INTEGER,starred INTEGER NOT NULL DEFAULT 0,pinned INTEGER NOT NULL DEFAULT 0,has_phone_number INTEGER NOT NULL DEFAULT 0,lookup TEXT,status_update_id INTEGER REFERENCES data(_id),contact_last_updated_timestamp INTEGER);
+
+INSERT INTO "contacts" VALUES(1,1,NULL,NULL,NULL,0,4,9940760264,0,0,1,NULL,NULL,9940760265);
+INSERT INTO "contacts" VALUES(2,2,NULL,NULL,NULL,0,0,0,0,0,1,NULL,NULL,9940366668);
+
+--CREATE TABLE raw_contacts (_id INTEGER PRIMARY KEY AUTOINCREMENT,account_id INTEGER REFERENCES accounts(_id),sourceid TEXT,backup_id TEXT,raw_contact_is_read_only INTEGER NOT NULL DEFAULT 0,version INTEGER NOT NULL DEFAULT 1,dirty INTEGER NOT NULL DEFAULT 0,deleted INTEGER NOT NULL DEFAULT 0,metadata_dirty INTEGER NOT NULL DEFAULT 0,contact_id INTEGER REFERENCES contacts(_id),aggregation_mode INTEGER NOT NULL DEFAULT 0,aggregation_needed INTEGER NOT NULL DEFAULT 1,custom_ringtone TEXT,send_to_voicemail INTEGER NOT NULL DEFAULT 0,times_contacted INTEGER NOT NULL DEFAULT 0,last_time_contacted INTEGER,starred INTEGER NOT NULL DEFAULT 0,pinned INTEGER NOT NULL DEFAULT 0,display_name TEXT,display_name_alt TEXT,display_name_source INTEGER NOT NULL DEFAULT 0,phonetic_name TEXT,phonetic_name_style TEXT,sort_key TEXT COLLATE PHONEBOOK,phonebook_label TEXT,phonebook_bucket INTEGER,sort_key_alt TEXT COLLATE PHONEBOOK,phonebook_label_alt TEXT,phonebook_bucket_alt INTEGER,name_verified INTEGER NOT NULL DEFAULT 0,sync1 TEXT, sync2 TEXT, sync3 TEXT, sync4 TEXT );
+
+INSERT INTO "raw_contacts" VALUES(1,1,NULL,NULL,0,3,1,0,0,1,0,0,NULL,0,4,9940760264,0,0,'Test','Test',40,NULL,'0','Test','T',20,'Test','T',20,0,NULL,NULL,NULL,NULL);
+INSERT INTO "raw_contacts" VALUES(2,1,NULL,NULL,0,3,1,0,0,2,0,0,NULL,0,0,NULL,0,0,'Test2','Test2',40,NULL,'0','Test2','T',20,'Test2','T',20,0,NULL,NULL,NULL,NULL);
+
+--CREATE TABLE data (_id INTEGER PRIMARY KEY AUTOINCREMENT,package_id INTEGER REFERENCES package(_id),mimetype_id INTEGER REFERENCES mimetype(_id) NOT NULL,raw_contact_id INTEGER REFERENCES raw_contacts(_id) NOT NULL,hash_id TEXT,is_read_only INTEGER NOT NULL DEFAULT 0,is_primary INTEGER NOT NULL DEFAULT 0,is_super_primary INTEGER NOT NULL DEFAULT 0,data_version INTEGER NOT NULL DEFAULT 0,data1 TEXT,data2 TEXT,data3 TEXT,data4 TEXT,data5 TEXT,data6 TEXT,data7 TEXT,data8 TEXT,data9 TEXT,data10 TEXT,data11 TEXT,data12 TEXT,data13 TEXT,data14 TEXT,data15 TEXT,data_sync1 TEXT, data_sync2 TEXT, data_sync3 TEXT, data_sync4 TEXT, carrier_presence INTEGER NOT NULL DEFAULT 0 );
+
+INSERT INTO "data" VALUES(1,NULL,5,1,NULL,0,0,0,0,'555','2',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);
+
+--CREATE TABLE data_usage_stat(stat_id INTEGER PRIMARY KEY AUTOINCREMENT, data_id INTEGER NOT NULL, usage_type INTEGER NOT NULL DEFAULT 0, times_used INTEGER NOT NULL DEFAULT 0, last_time_used INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(data_id) REFERENCES data(_id));
+
+INSERT INTO "data_usage_stat" VALUES(1,1,0,4,9940760264);
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
index 9b249d0..c155734 100644
--- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
@@ -18,6 +18,7 @@
import static com.android.providers.contacts.ContactsActor.PACKAGE_GREY;
import static com.android.providers.contacts.TestUtils.cv;
+import static com.android.providers.contacts.TestUtils.dumpCursor;
import android.accounts.Account;
import android.content.ContentProvider;
@@ -1095,6 +1096,9 @@
assertCursorValues(c, values);
} catch (Error e) {
TestUtils.dumpCursor(c);
+
+ // Dump with no selection.
+ TestUtils.dumpUri(mResolver, uri);
throw e;
} finally {
c.close();
@@ -1158,7 +1162,7 @@
}
}
- private void assertCursorValuesOrderly(Cursor cursor, ContentValues... expectedValues) {
+ public static void assertCursorValuesOrderly(Cursor cursor, ContentValues... expectedValues) {
StringBuilder message = new StringBuilder();
cursor.moveToPosition(-1);
for (ContentValues v : expectedValues) {
@@ -1188,7 +1192,7 @@
return true;
}
- private boolean equalsWithExpectedValues(Cursor cursor, ContentValues expectedValues,
+ private static boolean equalsWithExpectedValues(Cursor cursor, ContentValues expectedValues,
StringBuilder msgBuffer) {
for (String column : expectedValues.keySet()) {
int index = cursor.getColumnIndex(column);
@@ -1229,6 +1233,7 @@
final Cursor cursor = mResolver.query(uri, DATA_USAGE_PROJECTION, null, null,
null);
try {
+ dumpCursor(cursor);
assertCursorHasAnyRecordMatch(cursor, cv(Data.DATA1, data1, Data.TIMES_USED, timesUsed,
Data.LAST_TIME_USED, lastTimeUsed));
} finally {
@@ -1367,6 +1372,17 @@
long timeStamp = c.getLong(index);
assertTrue(Math.abs(time - timeStamp) < tolerance);
}
+
+ protected Uri insertRawContact(ContentValues values) {
+ return TestUtils.insertRawContact(mResolver,
+ getContactsProvider().getDatabaseHelper(), values);
+ }
+
+ protected Uri insertProfileRawContact(ContentValues values) {
+ return TestUtils.insertProfileRawContact(mResolver,
+ getContactsProvider().getProfileProviderForTest().getDatabaseHelper(), values);
+ }
+
/**
* A contact in the database, and the attributes used to create it. Construct using
* {@link GoldenContactBuilder#build()}.
diff --git a/tests/src/com/android/providers/contacts/BaseDatabaseHelperUpgradeTest.java b/tests/src/com/android/providers/contacts/BaseDatabaseHelperUpgradeTest.java
index 73303d0..0eb12ad 100644
--- a/tests/src/com/android/providers/contacts/BaseDatabaseHelperUpgradeTest.java
+++ b/tests/src/com/android/providers/contacts/BaseDatabaseHelperUpgradeTest.java
@@ -29,7 +29,7 @@
* Run the test like this: <code> runtest -c com.android.providers.contacts.BaseDatabaseHelperUpgradeTest
* contactsprov </code>
*/
-public class BaseDatabaseHelperUpgradeTest extends AndroidTestCase {
+public abstract class BaseDatabaseHelperUpgradeTest extends AndroidTestCase {
protected static final String INTEGER = "INTEGER";
protected static final String TEXT = "TEXT";
@@ -168,7 +168,14 @@
@Override
protected void setUp() throws Exception {
super.setUp();
- mDb = SQLiteDatabase.create(null);
+
+ final String filename = getDatabaseFilename();
+ if (filename == null) {
+ mDb = SQLiteDatabase.create(null);
+ } else {
+ getContext().deleteDatabase(filename);
+ mDb = SQLiteDatabase.openOrCreateDatabase(filename, null);
+ }
}
@Override
@@ -177,6 +184,8 @@
super.tearDown();
}
+ protected abstract String getDatabaseFilename();
+
protected void assertDatabaseStructureSameAsList(TableListEntry[] list, boolean isNewDatabase) {
for (TableListEntry entry : list) {
if (!entry.shouldBeInNewDb) {
diff --git a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java
index be3a73b..e0b5ea5 100644
--- a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java
+++ b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperTest.java
@@ -25,6 +25,7 @@
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;
+import com.android.providers.contacts.ContactsDatabaseHelper.LowRes;
import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
@@ -432,4 +433,56 @@
cursor.close();
}
}
+
+ private Integer getIntegerFromExpression(String expression) {
+ try (Cursor c = mDb.rawQuery("SELECT " + expression, null)) {
+ assertTrue(c.moveToPosition(0));
+ if (c.isNull(0)) {
+ return null;
+ }
+ return c.getInt(0);
+ }
+ }
+
+ private Integer checkGetTimesUsedExpression(Integer value) {
+ return getIntegerFromExpression(LowRes.getTimesUsedExpression(
+ value == null ? "NULL" : String.valueOf(value)));
+ }
+
+ public void testGetTimesUsedExpression() {
+ assertEquals((Object) 0, checkGetTimesUsedExpression(null));
+ assertEquals((Object) 0, checkGetTimesUsedExpression(-1));
+ assertEquals((Object) 0, checkGetTimesUsedExpression(-10));
+ assertEquals((Object) 0, checkGetTimesUsedExpression(0));
+ for (int i = 1; i < 10; i++) {
+ assertEquals("value=" + i, (Object) 1, checkGetTimesUsedExpression(i));
+ }
+ for (int i = 10; i < 20; i++) {
+ assertEquals("value=" + i, (Object) 10, checkGetTimesUsedExpression(i));
+ }
+ for (int i = 20; i < 30; i++) {
+ assertEquals("value=" + i, (Object) 20, checkGetTimesUsedExpression(i));
+ }
+
+ assertEquals((Object) 123450, checkGetTimesUsedExpression(123456));
+ }
+
+ private Integer checkGetLastTimeUsedExpression(Integer value) {
+ return getIntegerFromExpression(LowRes.getLastTimeUsedExpression(
+ value == null ? "NULL" : String.valueOf(value)));
+ }
+
+ public void testGetLastTimeUsedExpression() {
+ assertEquals((Object) null, checkGetLastTimeUsedExpression(null));
+ assertEquals((Object) 0, checkGetLastTimeUsedExpression(0));
+ assertEquals((Object) 0, checkGetLastTimeUsedExpression(1));
+ assertEquals((Object) 0, checkGetLastTimeUsedExpression(86399));
+ assertEquals((Object) 86400, checkGetLastTimeUsedExpression(86400));
+
+ for (int i = 1; i < 3; i++) {
+ assertEquals((Object) (86400 * i), checkGetLastTimeUsedExpression(86400 * i));
+ assertEquals((Object) (86400 * i), checkGetLastTimeUsedExpression(86400 * i + 1));
+ assertEquals((Object) (86400 * i), checkGetLastTimeUsedExpression(86400 * i + 86399));
+ }
+ }
}
diff --git a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
index d543b7e..185fa03 100644
--- a/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
+++ b/tests/src/com/android/providers/contacts/ContactsDatabaseHelperUpgradeTest.java
@@ -16,6 +16,11 @@
package com.android.providers.contacts;
+import static com.android.providers.contacts.TestUtils.createDatabaseSnapshot;
+import static com.android.providers.contacts.TestUtils.cv;
+import static com.android.providers.contacts.TestUtils.executeSqlFromAssetFile;
+
+import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.provider.BaseColumns;
import android.provider.CallLog.Calls;
@@ -84,9 +89,12 @@
private static final String CONTACTS2_DB_1108_ASSET_NAME = "upgradeTest/contacts2_1108.sql";
+ /**
+ * The helper instance. Note we just use it to call the upgrade method. The database
+ * hold by this instance is not used in this test.
+ */
private ContactsDatabaseHelper mHelper;
-
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -101,6 +109,11 @@
super.tearDown();
}
+ @Override
+ protected String getDatabaseFilename() {
+ return TestUtils.getContactsDatabaseFilename(getContext(), "-upgrade-test");
+ }
+
public void testDatabaseCreate() {
mHelper.onCreate(mDb);
assertDatabaseStructureSameAsList(TABLE_LIST, /* isNewDatabase =*/ true);
@@ -108,7 +121,10 @@
public void testDatabaseUpgrade_UpgradeToCurrent() {
create1108(mDb);
- mHelper.onUpgrade(mDb, 1108, mHelper.DATABASE_VERSION);
+ int oldVersion = upgrade(1108, 1200);
+ oldVersion = upgradeTo1201(oldVersion);
+ oldVersion = upgrade(oldVersion, ContactsDatabaseHelper.DATABASE_VERSION);
+
assertDatabaseStructureSameAsList(TABLE_LIST, /* isNewDatabase =*/ false);
}
@@ -138,8 +154,62 @@
return MY_VERSION;
}
+ private int upgradeTo1201(int upgradeFrom) {
+ final int MY_VERSION = 1201;
+
+ executeSqlFromAssetFile(getTestContext(), mDb, "upgradeTest/pre_upgrade1201.sql");
+
+ mHelper.onUpgrade(mDb, upgradeFrom, MY_VERSION);
+
+ try (Cursor c = mDb.rawQuery("select * from contacts order by _id", null)) {
+ BaseContactsProvider2Test.assertCursorValuesOrderly(c,
+ cv(Contacts._ID, 1,
+ "last_time_contacted", 0,
+ "x_last_time_contacted", 9940760264L,
+ "times_contacted", 0,
+ "x_times_contacted", 4
+ ),
+ cv(
+ "last_time_contacted", 0,
+ "x_last_time_contacted", 0,
+ "times_contacted", 0,
+ "x_times_contacted", 0
+ ));
+ }
+
+ try (Cursor c = mDb.rawQuery("select * from raw_contacts order by _id", null)) {
+ BaseContactsProvider2Test.assertCursorValuesOrderly(c,
+ cv("_id", 1,
+ "last_time_contacted", 0,
+ "x_last_time_contacted", 9940760264L,
+ "times_contacted", 0,
+ "x_times_contacted", 4
+ ),
+ cv(
+ "last_time_contacted", 0,
+ "x_last_time_contacted", 0,
+ "times_contacted", 0,
+ "x_times_contacted", 0
+ ));
+ }
+
+ try (Cursor c = mDb.rawQuery("select * from data_usage_stat", null)) {
+ BaseContactsProvider2Test.assertCursorValuesOrderly(c,
+ cv(
+ "last_time_used", 0,
+ "x_last_time_used", 9940760264L,
+ "times_used", 0,
+ "x_times_used", 4
+ ));
+ }
+
+ return MY_VERSION;
+ }
+
private int upgrade(int upgradeFrom, int upgradeTo) {
- mHelper.onUpgrade(mDb, upgradeFrom, upgradeTo);
+ if (upgradeFrom < upgradeTo) {
+ mHelper.onUpgrade(mDb, upgradeFrom, upgradeTo);
+ }
return upgradeTo;
}
@@ -148,15 +218,7 @@
* incrementally from this version.
*/
private void create1108(SQLiteDatabase db) {
- try (InputStream input = getTestContext().getAssets().open(CONTACTS2_DB_1108_ASSET_NAME);) {
- BufferedReader r = new BufferedReader(new InputStreamReader(input));
- String query;
- while ((query = r.readLine()) != null) {
- db.execSQL(query);
- }
- } catch (IOException e) {
- throw new RuntimeException(e.toString());
- }
+ executeSqlFromAssetFile(getTestContext(), db, CONTACTS2_DB_1108_ASSET_NAME);
}
/**
@@ -181,8 +243,10 @@
new TableColumn(Contacts.PHOTO_FILE_ID, INTEGER, false, null),
new TableColumn(Contacts.CUSTOM_RINGTONE, TEXT, false, null),
new TableColumn(Contacts.SEND_TO_VOICEMAIL, INTEGER, true, "0"),
- new TableColumn(Contacts.TIMES_CONTACTED, INTEGER, true, "0"),
- new TableColumn(Contacts.LAST_TIME_CONTACTED, INTEGER, false, null),
+ new TableColumn(Contacts.RAW_TIMES_CONTACTED, INTEGER, true, "0"),
+ new TableColumn(Contacts.RAW_LAST_TIME_CONTACTED, INTEGER, false, null),
+ new TableColumn(Contacts.LR_TIMES_CONTACTED, INTEGER, true, "0"),
+ new TableColumn(Contacts.LR_LAST_TIME_CONTACTED, INTEGER, false, null),
new TableColumn(Contacts.STARRED, INTEGER, true, "0"),
new TableColumn(Contacts.PINNED, INTEGER, true,
String.valueOf(PinnedPositions.UNPINNED)),
@@ -213,8 +277,10 @@
new TableColumn(RawContactsColumns.AGGREGATION_NEEDED, INTEGER, true, "1"),
new TableColumn(RawContacts.CUSTOM_RINGTONE, TEXT, false, null),
new TableColumn(RawContacts.SEND_TO_VOICEMAIL, INTEGER, true, "0"),
- new TableColumn(RawContacts.TIMES_CONTACTED, INTEGER, true, "0"),
- new TableColumn(RawContacts.LAST_TIME_CONTACTED, INTEGER, false, null),
+ new TableColumn(RawContacts.RAW_TIMES_CONTACTED, INTEGER, true, "0"),
+ new TableColumn(RawContacts.RAW_LAST_TIME_CONTACTED, INTEGER, false, null),
+ new TableColumn(RawContacts.LR_TIMES_CONTACTED, INTEGER, true, "0"),
+ new TableColumn(RawContacts.LR_LAST_TIME_CONTACTED, INTEGER, false, null),
new TableColumn(RawContacts.STARRED, INTEGER, true, "0"),
new TableColumn(RawContacts.PINNED, INTEGER, true,
String.valueOf(PinnedPositions.UNPINNED)),
@@ -464,8 +530,10 @@
new TableColumn(DataUsageStatColumns._ID, INTEGER, false, null),
new TableColumn(DataUsageStatColumns.DATA_ID, INTEGER, true, null),
new TableColumn(DataUsageStatColumns.USAGE_TYPE_INT, INTEGER, true, "0"),
- new TableColumn(DataUsageStatColumns.TIMES_USED, INTEGER, true, "0"),
- new TableColumn(DataUsageStatColumns.LAST_TIME_USED, INTEGER, true, "0"),
+ new TableColumn(DataUsageStatColumns.RAW_TIMES_USED, INTEGER, true, "0"),
+ new TableColumn(DataUsageStatColumns.RAW_LAST_TIME_USED, INTEGER, true, "0"),
+ new TableColumn(DataUsageStatColumns.LR_TIMES_USED, INTEGER, true, "0"),
+ new TableColumn(DataUsageStatColumns.LR_LAST_TIME_USED, INTEGER, true, "0"),
};
private static final TableColumn[] METADATA_SYNC_COLUMNS = new TableColumn[] {
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index f951f6d..8ba7f8c 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -16,7 +16,9 @@
package com.android.providers.contacts;
+import static com.android.providers.contacts.TestUtils.createDatabaseSnapshot;
import static com.android.providers.contacts.TestUtils.cv;
+import static com.android.providers.contacts.TestUtils.dumpCursor;
import android.accounts.Account;
import android.content.ContentProviderOperation;
@@ -254,8 +256,8 @@
Contacts.CONTACT_STATUS_LABEL,
Contacts.CONTACT_STATUS_ICON,
Contacts.CONTACT_LAST_UPDATED_TIMESTAMP,
- DataUsageStatColumns.TIMES_USED,
- DataUsageStatColumns.LAST_TIME_USED,
+ DataUsageStatColumns.LR_TIMES_USED,
+ DataUsageStatColumns.LR_LAST_TIME_USED,
});
}
@@ -299,8 +301,8 @@
Contacts.CONTACT_STATUS_LABEL,
Contacts.CONTACT_STATUS_ICON,
Contacts.CONTACT_LAST_UPDATED_TIMESTAMP,
- DataUsageStatColumns.TIMES_USED,
- DataUsageStatColumns.LAST_TIME_USED,
+ DataUsageStatColumns.LR_TIMES_USED,
+ DataUsageStatColumns.LR_LAST_TIME_USED,
Phone.NUMBER,
Phone.TYPE,
Phone.LABEL,
@@ -650,8 +652,8 @@
Contacts.CONTACT_STATUS_ICON,
Contacts.CONTACT_LAST_UPDATED_TIMESTAMP,
GroupMembership.GROUP_SOURCE_ID,
- DataUsageStatColumns.TIMES_USED,
- DataUsageStatColumns.LAST_TIME_USED,
+ Contacts.Entity.TIMES_USED,
+ Contacts.Entity.LAST_TIME_USED,
});
}
@@ -958,7 +960,8 @@
values.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED);
values.put(RawContacts.CUSTOM_RINGTONE, "d");
values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
- values.put(RawContacts.LAST_TIME_CONTACTED, 12345);
+ values.put(RawContacts.LAST_TIME_CONTACTED, 86400 + 123);
+ values.put(RawContacts.TIMES_CONTACTED, 12);
values.put(RawContacts.STARRED, 1);
values.put(RawContacts.SYNC1, "e");
values.put(RawContacts.SYNC2, "f");
@@ -968,6 +971,9 @@
Uri rowUri = mResolver.insert(RawContacts.CONTENT_URI, values);
long rawContactId = ContentUris.parseId(rowUri);
+ values.put(RawContacts.LAST_TIME_CONTACTED, 86400);
+ values.put(RawContacts.TIMES_CONTACTED, 0); // This is not insertable.
+
assertStoredValues(rowUri, values);
assertSelection(RawContacts.CONTENT_URI, values, RawContacts._ID, rawContactId);
assertNetworkNotified(true);
@@ -1290,11 +1296,11 @@
ContentValues values = new ContentValues();
values.put(RawContacts.CUSTOM_RINGTONE, "d");
values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
- values.put(RawContacts.LAST_TIME_CONTACTED, 12345);
+ values.put(RawContacts.LAST_TIME_CONTACTED, 86400 + 5);
values.put(RawContacts.TIMES_CONTACTED, 54321);
values.put(RawContacts.STARRED, 1);
- Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
+ Uri rawContactUri = insertRawContact(values);
long rawContactId = ContentUris.parseId(rawContactUri);
DataUtil.insertStructuredName(mResolver, rawContactId, "Meghan", "Knox");
@@ -1314,8 +1320,8 @@
values.put(Contacts.DISPLAY_NAME, "Meghan Knox");
values.put(Contacts.CUSTOM_RINGTONE, "d");
values.put(Contacts.SEND_TO_VOICEMAIL, 1);
- values.put(Contacts.LAST_TIME_CONTACTED, 12345);
- values.put(Contacts.TIMES_CONTACTED, 54321);
+ values.put(Contacts.LAST_TIME_CONTACTED, 86400);
+ values.put(Contacts.TIMES_CONTACTED, 54320);
values.put(Contacts.STARRED, 1);
assertStoredValues(ContentUris.withAppendedId(Phone.CONTENT_URI, phoneId), values);
@@ -2476,11 +2482,11 @@
ContentValues values = new ContentValues();
values.put(RawContacts.CUSTOM_RINGTONE, "d");
values.put(RawContacts.SEND_TO_VOICEMAIL, 1);
- values.put(RawContacts.LAST_TIME_CONTACTED, 12345);
+ values.put(RawContacts.LAST_TIME_CONTACTED, 86400 + 5);
values.put(RawContacts.TIMES_CONTACTED, 54321);
values.put(RawContacts.STARRED, 1);
- Uri rawContactUri = mResolver.insert(RawContacts.CONTENT_URI, values);
+ Uri rawContactUri = insertRawContact(values);
final long rawContactId = ContentUris.parseId(rawContactUri);
DataUtil.insertStructuredName(mResolver, rawContactId, "Meghan", "Knox");
@@ -2499,8 +2505,8 @@
values.put(Contacts.DISPLAY_NAME, "Meghan Knox");
values.put(Contacts.CUSTOM_RINGTONE, "d");
values.put(Contacts.SEND_TO_VOICEMAIL, 1);
- values.put(Contacts.LAST_TIME_CONTACTED, 12345);
- values.put(Contacts.TIMES_CONTACTED, 54321);
+ values.put(Contacts.LAST_TIME_CONTACTED, 86400);
+ values.put(Contacts.TIMES_CONTACTED, 54320);
values.put(Contacts.STARRED, 1);
assertStoredValues(Email.CONTENT_URI, values);
@@ -2855,7 +2861,7 @@
/**
* Tests {@link DataUsageFeedback} correctly bucketize contacts using each
- * {@link DataUsageStatColumns#LAST_TIME_USED}
+ * {@link DataUsageStatColumns#RAW_LAST_TIME_USED}
*/
public void testEmailFilterSortOrderWithOldHistory() {
long rawContactId1 = RawContactUtil.createRawContact(mResolver);
@@ -3010,7 +3016,8 @@
assertStoredValue(dataUri2, Data.IS_SUPER_PRIMARY, 0);
final Uri dataUriWithUsageType = Data.CONTENT_URI.buildUpon().appendQueryParameter(
DataUsageFeedback.USAGE_TYPE, usageTypeString).build();
- assertDataUsageCursorContains(dataUriWithUsageType, emailAddress, timesUsed, lastTimeUsed);
+ assertDataUsageCursorContains(dataUriWithUsageType, emailAddress, 1,
+ 1111111 / 86400 * 86400); // Returned values are lowres.
// Update AggregationException table.
RawContactInfo aggregationContact = new RawContactInfo(
@@ -3475,6 +3482,7 @@
StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO);
Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+ values.put(Contacts.TIMES_CONTACTED, 1); // low res.
assertStoredValues(contactUri, values);
assertSelection(Contacts.CONTENT_URI, values, Contacts._ID, contactId);
}
@@ -3487,6 +3495,9 @@
values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE);
values.put(Contacts.CONTACT_CHAT_CAPABILITY, StatusUpdates.CAPABILITY_HAS_CAMERA);
Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
+
+ values.put(Contacts.TIMES_CONTACTED, 1); // low res.
+
assertStoredValuesWithProjection(contactUri, values);
assertSelectionWithProjection(Contacts.CONTENT_URI, values, Contacts._ID, contactId);
}
@@ -3509,6 +3520,7 @@
values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE);
Uri filterUri1 = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, "goulash");
+ values.put(Contacts.TIMES_CONTACTED, 1); // low res.
assertStoredValuesWithProjection(filterUri1, values);
assertContactFilter(contactId, "goolash");
@@ -3543,6 +3555,7 @@
values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE);
Uri filterUri1 = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, "goog411@acme.com");
+ values.put(Contacts.TIMES_CONTACTED, 1); // low res.
assertStoredValuesWithProjection(filterUri1, values);
assertContactFilter(contactId, "goog");
@@ -3569,6 +3582,7 @@
values.put(Contacts.CONTACT_PRESENCE, StatusUpdates.INVISIBLE);
Uri filterUri1 = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, "18004664411");
+ values.put(Contacts.TIMES_CONTACTED, 1); // low res.
assertStoredValuesWithProjection(filterUri1, values);
assertContactFilter(contactId, "18004664411");
@@ -3600,7 +3614,7 @@
StatusUpdates.CAPABILITY_HAS_CAMERA);
ContentValues values3 = new ContentValues();
final String phoneNumber3 = "18004664413";
- final int timesContacted3 = 5;
+ final int timesContacted3 = 9;
createContact(values3, "Lotta", "Calling", phoneNumber3,
"c@acme.com", StatusUpdates.AWAY, timesContacted3, 0, 0,
StatusUpdates.CAPABILITY_HAS_VIDEO);
@@ -3616,6 +3630,8 @@
// Send feedback for the 3rd phone number, pretending we called that person via phone.
sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3);
+ values3.put(Contacts.TIMES_CONTACTED, 10); // Low res.
+
// After the feedback, 3rd contact should be shown after starred one.
assertStoredValuesOrderly(Contacts.CONTENT_STREQUENT_URI,
new ContentValues[] { values4, values3 });
@@ -3625,6 +3641,7 @@
sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values1);
// After the feedback, 1st and 3rd contacts should be shown after starred one.
+ values1.put(Contacts.TIMES_CONTACTED, 1); // Low res.
assertStoredValuesOrderly(Contacts.CONTENT_STREQUENT_URI,
new ContentValues[] { values4, values1, values3 });
@@ -3659,6 +3676,10 @@
final ContentValues values6 = new ContentValues(values4);
values6.put(Phone.NUMBER, phoneNumber6);
+ values4.put(Contacts.TIMES_CONTACTED, 1); // Low res.
+ values5.put(Contacts.TIMES_CONTACTED, 1); // Low res.
+ values6.put(Contacts.TIMES_CONTACTED, 1); // Low res.
+
// Phone only strequent should return all phone numbers belonging to the 4th contact,
// and then contact 3.
assertStoredValuesOrderly(phoneOnlyStrequentUri, new ContentValues[] {values5, values6,
@@ -3667,6 +3688,8 @@
// Send feedback for the 2rd phone number, pretending we send the person a SMS message.
sendFeedback(phoneNumber2, DataUsageFeedback.USAGE_TYPE_SHORT_TEXT, values1);
+ values1.put(Contacts.TIMES_CONTACTED, 1); // Low res.
+
// SMS feedback shouldn't affect phone-only results.
assertStoredValuesOrderly(phoneOnlyStrequentUri, new ContentValues[] {values5, values6,
values4, values3});
@@ -3749,6 +3772,8 @@
// Contact cid1 again, but it's an email, not a phone call.
updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, did1e);
+ updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, did1e);
+ updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, did1e);
// Contacts in this bucket are considered more than 3 days old
sMockClock.setCurrentTimeMillis(fourDaysAgoInMillis);
@@ -3821,12 +3846,15 @@
sendFeedback(email2, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values2);
sendFeedback(email2, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values2);
+ values1.put(Contacts.TIMES_CONTACTED, 1); // low res.
+ values2.put(Contacts.TIMES_CONTACTED, 1); // low res.
assertStoredValues(Contacts.CONTENT_FREQUENT_URI, new ContentValues[] {values2, values1});
- // Three times
- sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3);
- sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3);
- sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3);
+ for (int i = 0; i < 10; i++) {
+ sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3);
+ }
+
+ values3.put(Contacts.TIMES_CONTACTED, 10); // low res.
assertStoredValues(Contacts.CONTENT_FREQUENT_URI,
new ContentValues[] {values3, values2, values1});
@@ -3887,34 +3915,51 @@
sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values1);
- assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 1, 100);
+ assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 1, 0);
- sMockClock.setCurrentTimeMillis(111);
+ sMockClock.setCurrentTimeMillis(86400 + 123);
sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values1);
- assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 2, 111);
+ assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 1, 86400);
- sMockClock.setCurrentTimeMillis(123);
- sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_SHORT_TEXT, values1);
+ sMockClock.setCurrentTimeMillis(86400 * 3 + 123);
+ for (int i = 0; i < 11; i++) {
+ sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_SHORT_TEXT, values1);
+ }
- assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 3, 123);
+ // Note here, "a@acme.com" has two data stats rows, 2 and 11. What we get here's the sum
+ // of the lowres values, so # times will be 11, instead of 10 (which is the lowres of the
+ // sum).
+ assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 11, 86400 * 3);
final Uri dataUriWithUsageTypeLongText = Data.CONTENT_URI.buildUpon().appendQueryParameter(
DataUsageFeedback.USAGE_TYPE, DataUsageFeedback.USAGE_TYPE_LONG_TEXT).build();
- assertDataUsageCursorContains(dataUriWithUsageTypeLongText, "a@acme.com", 2, 111);
+ assertDataUsageCursorContains(dataUriWithUsageTypeLongText, "a@acme.com", 1, 86400 * 1);
- sMockClock.setCurrentTimeMillis(200);
+ sMockClock.setCurrentTimeMillis(86400 * 4 + 123);
sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_CALL, values1);
sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_CALL, values1);
sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_CALL, values1);
- assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 6, 200);
+ assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 12, 86400 * 4);
+
+ sMockClock.setCurrentTimeMillis(86400 * 5 + 123);
+ for (int i = 0; i < 10; i++) {
+ sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_CALL, values1);
+ }
+ assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 21, 86400 * 5);
+
+ sMockClock.setCurrentTimeMillis(86400 * 6 + 123);
+ for (int i = 0; i < 10; i++) {
+ sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_CALL, values1);
+ }
+ assertDataUsageCursorContains(Data.CONTENT_URI, "a@acme.com", 31, 86400 * 6);
final Uri dataUriWithUsageTypeCall = Data.CONTENT_URI.buildUpon().appendQueryParameter(
DataUsageFeedback.USAGE_TYPE, DataUsageFeedback.USAGE_TYPE_CALL).build();
- assertDataUsageCursorContains(dataUriWithUsageTypeCall, "a@acme.com", 3, 200);
+ assertDataUsageCursorContains(dataUriWithUsageTypeCall, "a@acme.com", 20, 86400 * 6);
}
public void testQueryContactGroup() {
@@ -3934,6 +3979,7 @@
Cursor c = mResolver.query(filterUri1, null, null, null, Contacts._ID);
assertEquals(1, c.getCount());
c.moveToFirst();
+ dumpCursor(c);
assertCursorValues(c, values1);
c.close();
@@ -4115,6 +4161,9 @@
long nonProfileRawContactId = createBasicNonProfileContact(nonProfileValues);
long nonProfileContactId = queryContactId(nonProfileRawContactId);
+ nonProfileValues.put(Contacts.TIMES_CONTACTED, 1); // low res.
+ profileValues.put(Contacts.TIMES_CONTACTED, 1); // low res.
+
assertStoredValues(Contacts.CONTENT_URI, nonProfileValues);
assertSelection(Contacts.CONTENT_URI, nonProfileValues, Contacts._ID, nonProfileContactId);
@@ -4128,7 +4177,7 @@
// Create a non-profile contact - this should be returned.
ContentValues nonProfileValues = new ContentValues();
createBasicNonProfileContact(nonProfileValues);
-
+ nonProfileValues.put(Contacts.TIMES_CONTACTED, 1); // low res
assertStoredValues(Contacts.CONTENT_URI, new ContentValues[] {nonProfileValues});
}
@@ -4136,6 +4185,7 @@
ContentValues profileValues = new ContentValues();
createBasicProfileContact(profileValues);
+ profileValues.put(Contacts.TIMES_CONTACTED, 1); // low res
assertStoredValues(Profile.CONTENT_URI, profileValues);
}
@@ -4184,6 +4234,7 @@
// The raw contact view doesn't include the photo ID.
profileValues.remove(Contacts.PHOTO_ID);
+ profileValues.put(Contacts.TIMES_CONTACTED, 1); // low res
assertStoredValues(Profile.CONTENT_RAW_CONTACTS_URI, profileValues);
}
@@ -4193,6 +4244,7 @@
// The raw contact view doesn't include the photo ID.
profileValues.remove(Contacts.PHOTO_ID);
+ profileValues.put(Contacts.TIMES_CONTACTED, 1); // low res
assertStoredValues(ContentUris.withAppendedId(
Profile.CONTENT_RAW_CONTACTS_URI, profileRawContactId), profileValues);
}
@@ -8747,8 +8799,7 @@
// Now, there's a single frequent. (contact 1)
assertRowCount(1, Contacts.CONTENT_STREQUENT_URI, null, null);
- // time = startTime + 1
- sMockClock.advance();
+ sMockClock.advanceDay();
// Test 2. touch data 1a, 2a and 3a
updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_LONG_TEXT, did1a, did2a, did3a);
@@ -8756,8 +8807,7 @@
// Now, contact 1 and 3 are in frequent.
assertRowCount(2, Contacts.CONTENT_STREQUENT_URI, null, null);
- // time = startTime + 2
- sMockClock.advance();
+ sMockClock.advanceDay();
// Test 2. touch data 2p (call)
updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_CALL, did2p);
@@ -8765,71 +8815,70 @@
// There're still two frequent.
assertRowCount(2, Contacts.CONTENT_STREQUENT_URI, null, null);
- // time = startTime + 3
- sMockClock.advance();
+ sMockClock.advanceDay();
// Test 3. touch data 2p and 3p (short text)
updateDataUsageFeedback(DataUsageFeedback.USAGE_TYPE_SHORT_TEXT, did2p, did3p);
// Let's check the tables.
-
+// TODO more tests?
// Fist, check the data_usage_stat table, which has no public URI.
assertStoredValuesDb("SELECT " + DataUsageStatColumns.DATA_ID +
"," + DataUsageStatColumns.USAGE_TYPE_INT +
- "," + DataUsageStatColumns.TIMES_USED +
- "," + DataUsageStatColumns.LAST_TIME_USED +
+ "," + DataUsageStatColumns.RAW_TIMES_USED +
+ "," + DataUsageStatColumns.RAW_LAST_TIME_USED +
" FROM " + Tables.DATA_USAGE_STAT, null,
cv(DataUsageStatColumns.DATA_ID, did1a,
DataUsageStatColumns.USAGE_TYPE_INT,
DataUsageStatColumns.USAGE_TYPE_INT_LONG_TEXT,
- DataUsageStatColumns.TIMES_USED, 2,
- DataUsageStatColumns.LAST_TIME_USED, startTime + 1
+ DataUsageStatColumns.RAW_TIMES_USED, 2,
+ DataUsageStatColumns.RAW_LAST_TIME_USED, startTime + 86400
),
cv(DataUsageStatColumns.DATA_ID, did2a,
DataUsageStatColumns.USAGE_TYPE_INT,
DataUsageStatColumns.USAGE_TYPE_INT_LONG_TEXT,
- DataUsageStatColumns.TIMES_USED, 1,
- DataUsageStatColumns.LAST_TIME_USED, startTime + 1
+ DataUsageStatColumns.RAW_TIMES_USED, 1,
+ DataUsageStatColumns.RAW_LAST_TIME_USED, startTime + 86400
),
cv(DataUsageStatColumns.DATA_ID, did3a,
DataUsageStatColumns.USAGE_TYPE_INT,
DataUsageStatColumns.USAGE_TYPE_INT_LONG_TEXT,
- DataUsageStatColumns.TIMES_USED, 1,
- DataUsageStatColumns.LAST_TIME_USED, startTime + 1
+ DataUsageStatColumns.RAW_TIMES_USED, 1,
+ DataUsageStatColumns.RAW_LAST_TIME_USED, startTime + 86400
),
cv(DataUsageStatColumns.DATA_ID, did2p,
DataUsageStatColumns.USAGE_TYPE_INT,
DataUsageStatColumns.USAGE_TYPE_INT_CALL,
- DataUsageStatColumns.TIMES_USED, 1,
- DataUsageStatColumns.LAST_TIME_USED, startTime + 2
+ DataUsageStatColumns.RAW_TIMES_USED, 1,
+ DataUsageStatColumns.RAW_LAST_TIME_USED, startTime + 86400 * 2
),
cv(DataUsageStatColumns.DATA_ID, did2p,
DataUsageStatColumns.USAGE_TYPE_INT,
DataUsageStatColumns.USAGE_TYPE_INT_SHORT_TEXT,
- DataUsageStatColumns.TIMES_USED, 1,
- DataUsageStatColumns.LAST_TIME_USED, startTime + 3
+ DataUsageStatColumns.RAW_TIMES_USED, 1,
+ DataUsageStatColumns.RAW_LAST_TIME_USED, startTime + 86400 * 3
),
cv(DataUsageStatColumns.DATA_ID, did3p,
DataUsageStatColumns.USAGE_TYPE_INT,
DataUsageStatColumns.USAGE_TYPE_INT_SHORT_TEXT,
- DataUsageStatColumns.TIMES_USED, 1,
- DataUsageStatColumns.LAST_TIME_USED, startTime + 3
+ DataUsageStatColumns.RAW_TIMES_USED, 1,
+ DataUsageStatColumns.RAW_LAST_TIME_USED, startTime + 86400 * 3
)
);
// Next, check the raw_contacts table
assertStoredValuesWithProjection(RawContacts.CONTENT_URI,
cv(RawContacts._ID, rid1,
- RawContacts.TIMES_CONTACTED, 2,
- RawContacts.LAST_TIME_CONTACTED, startTime + 1
+ RawContacts.TIMES_CONTACTED, 1,
+ RawContacts.LAST_TIME_CONTACTED, (startTime + 86400) / 86400 * 86400
),
cv(RawContacts._ID, rid2,
- RawContacts.TIMES_CONTACTED, 3,
- RawContacts.LAST_TIME_CONTACTED, startTime + 3
+ RawContacts.TIMES_CONTACTED, 1,
+ RawContacts.LAST_TIME_CONTACTED, (startTime + 86400 * 3) / 86400 * 86400
),
cv(RawContacts._ID, rid3,
- RawContacts.TIMES_CONTACTED, 2,
- RawContacts.LAST_TIME_CONTACTED, startTime + 3
+ RawContacts.TIMES_CONTACTED, 1,
+ RawContacts.LAST_TIME_CONTACTED, (startTime + 86400 * 3) / 86400 * 86400
),
cv(RawContacts._ID, rid4,
RawContacts.TIMES_CONTACTED, 0,
@@ -8844,16 +8893,16 @@
// at once.
assertStoredValuesWithProjection(Contacts.CONTENT_URI,
cv(Contacts._ID, cid1,
- Contacts.TIMES_CONTACTED, 4,
- Contacts.LAST_TIME_CONTACTED, startTime + 3
+ Contacts.TIMES_CONTACTED, 1,
+ Contacts.LAST_TIME_CONTACTED, (startTime + 86400 * 3) / 86400 * 86400
),
cv(Contacts._ID, cid3,
- Contacts.TIMES_CONTACTED, 2,
- Contacts.LAST_TIME_CONTACTED, startTime + 3
+ Contacts.TIMES_CONTACTED, 1,
+ Contacts.LAST_TIME_CONTACTED, (startTime + 86400 * 3) / 86400 * 86400
),
cv(Contacts._ID, cid4,
Contacts.TIMES_CONTACTED, 0,
- Contacts.LAST_TIME_CONTACTED, 0 // For contacts, the default is 0, not null.
+ Contacts.LAST_TIME_CONTACTED, 0
)
);
@@ -9688,10 +9737,13 @@
values.put(RawContacts.CUSTOM_RINGTONE, "beethoven5");
values.put(RawContacts.TIMES_CONTACTED, timesContacted);
- Uri insertionUri = isUserProfile
- ? Profile.CONTENT_RAW_CONTACTS_URI
- : RawContacts.CONTENT_URI;
- Uri rawContactUri = mResolver.insert(insertionUri, values);
+ Uri rawContactUri;
+ if (isUserProfile) {
+ rawContactUri = insertProfileRawContact(values);
+ } else {
+ rawContactUri = insertRawContact(values);
+ }
+
long rawContactId = ContentUris.parseId(rawContactUri);
Uri photoUri = insertPhoto(rawContactId);
long photoId = ContentUris.parseId(photoUri);
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2TransactionTest.java b/tests/src/com/android/providers/contacts/ContactsProvider2TransactionTest.java
index 6a82bf9..03c9e75 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2TransactionTest.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2TransactionTest.java
@@ -60,7 +60,7 @@
*/
public void testTransactionCallback_insert() {
- final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 12345);
+ final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 86400);
// Insert a raw contact.
mProvider.resetTrasactionCallbackCalledFlags();
@@ -87,14 +87,14 @@
*/
public void testTransactionCallback_update() {
- final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 12345);
+ final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 86400);
// Make sure to create a raw contact and a profile raw contact.
mResolver.insert(RawContacts.CONTENT_URI, values);
mResolver.insert(Profile.CONTENT_RAW_CONTACTS_URI, values);
values.clear();
- values.put(RawContacts.LAST_TIME_CONTACTED, 99999);
+ values.put(RawContacts.LAST_TIME_CONTACTED, 86400 * 2);
// Update all raw contacts.
mProvider.resetTrasactionCallbackCalledFlags();
@@ -121,7 +121,7 @@
*/
public void testTransactionCallback_delete() {
- final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 12345);
+ final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 86400);
// Make sure to create a raw contact and a profile raw contact.
mResolver.insert(RawContacts.CONTENT_URI, values);
@@ -150,7 +150,7 @@
*/
public void testTransactionCallback_bulkInsert() {
- final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 12345);
+ final ContentValues values = cv(RawContacts.LAST_TIME_CONTACTED, 86400);
// Insert a raw contact.
mProvider.resetTrasactionCallbackCalledFlags();
@@ -179,7 +179,7 @@
ContentProviderOperation.Builder b;
b = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
b.withValue(RawContacts.STARRED, 1);
- b.withValue(RawContacts.TIMES_CONTACTED, 200001);
+ b.withValue(RawContacts.LAST_TIME_CONTACTED, 86400 * 21);
ops.add(b.build());
b = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -197,7 +197,7 @@
private void checkStoredContact() {
assertStoredValues(Contacts.CONTENT_URI, cv(
Contacts.DISPLAY_NAME, "Regular Contact",
- RawContacts.TIMES_CONTACTED, 200001
+ RawContacts.LAST_TIME_CONTACTED, 86400 * 21
));
}
@@ -208,7 +208,7 @@
ContentProviderOperation.Builder b;
b = ContentProviderOperation.newInsert(Profile.CONTENT_RAW_CONTACTS_URI);
b.withValue(RawContacts.STARRED, 1);
- b.withValue(RawContacts.TIMES_CONTACTED, 100001);
+ b.withValue(RawContacts.LAST_TIME_CONTACTED, 86400 * 11);
ops.add(b.build());
b = ContentProviderOperation.newInsert(Data.CONTENT_URI);
@@ -227,7 +227,7 @@
private void checkStoredProfile() {
assertStoredValues(Profile.CONTENT_URI, cv(
Contacts.DISPLAY_NAME, "Profile Contact",
- RawContacts.TIMES_CONTACTED, 100001
+ RawContacts.LAST_TIME_CONTACTED, 86400 * 11
));
}
diff --git a/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java b/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java
index 378c9eb..21d148c 100644
--- a/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java
+++ b/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java
@@ -16,6 +16,9 @@
package com.android.providers.contacts;
+import static com.android.providers.contacts.TestUtils.dumpTable;
+import static com.android.providers.contacts.TestUtils.dumpUri;
+
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
@@ -64,12 +67,23 @@
return Contacts.AUTHORITY + ";" + ContactsContract.AUTHORITY;
}
+ private static ContentValues noStats(ContentValues v) {
+ final ContentValues ret = new ContentValues(v);
+ ret.put(People.TIMES_CONTACTED, 0);
+ ret.put(People.LAST_TIME_CONTACTED, 0);
+ return ret;
+ }
+
public void testPeopleInsert() {
ContentValues values = new ContentValues();
putContactValues(values);
Uri uri = mResolver.insert(People.CONTENT_URI, values);
+
+ values = noStats(values);
+
assertStoredValues(uri, values);
+
assertSelection(People.CONTENT_URI, values, "people", People._ID, ContentUris.parseId(uri));
}
@@ -78,6 +92,7 @@
putContactValues(values);
Uri uri = mResolver.insert(People.CONTENT_URI, values);
+ values = noStats(values);
long personId = ContentUris.parseId(uri);
assertStoredValues(uri, values);
assertSelection(People.CONTENT_URI, values, "people", People._ID, personId);
@@ -85,11 +100,13 @@
values.clear();
putContactValues2(values);
mResolver.update(uri, values, null, null);
+ values = noStats(values);
assertStoredValues(uri, values);
values.clear();
putContactValues(values);
mResolver.update(People.CONTENT_URI, values, People._ID + "=" + personId, null);
+ values = noStats(values);
assertStoredValues(uri, values);
}
@@ -207,6 +224,7 @@
values.clear();
putContactValuesExceptName(values);
+ values = noStats(values);
values.put(People.PRIMARY_PHONE_ID, ContentUris.parseId(phoneUri1));
assertStoredValues(phoneUri2, values);
@@ -279,6 +297,7 @@
values.clear();
putContactValuesExceptName(values);
+ values = noStats(values);
values.put(People.PRIMARY_EMAIL_ID, ContentUris.parseId(emailUri1));
assertStoredValues(emailUri2, values);
@@ -314,9 +333,9 @@
int timesContactedAfter =
Integer.parseInt(getStoredValue(personUri, People.TIMES_CONTACTED));
- assertTrue(lastContacted >= timeBefore);
- assertTrue(lastContacted <= timeAfter);
- assertEquals(timesContactedAfter, timesContactedBefore + 1);
+ // No longer supported as of O.
+ assertEquals(0, lastContacted);
+ assertEquals(0, timesContactedAfter);
}
public void testOrganizationsInsert() {
@@ -401,6 +420,7 @@
// The result is joined with People
putContactValues(expectedResults[0]);
+ expectedResults[0] = noStats(expectedResults[0]);
assertStoredValues(uri, expectedResults);
assertSelection(Phones.CONTENT_URI, values, "phones",
Phones._ID, ContentUris.parseId(uri));
@@ -412,6 +432,7 @@
// Now the person should be joined with Phone
values.clear();
putContactValues(values);
+ values = noStats(values);
values.put(People.TYPE, Phones.TYPE_CUSTOM);
values.put(People.LABEL, "Directory");
values.put(People.NUMBER, "1-800-4664-411");
@@ -541,6 +562,9 @@
// The result is joined with People
putContactValues(values);
+
+ values = noStats(values);
+
assertStoredValues(uri, values);
assertSelection(ContactMethods.CONTENT_URI, values, "contact_methods",
ContactMethods._ID, ContentUris.parseId(uri));
diff --git a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
index c96c5b2..6ed8711 100644
--- a/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
+++ b/tests/src/com/android/providers/contacts/SynchronousContactsProvider2.java
@@ -54,6 +54,10 @@
return new SynchronousProfileProvider(this);
}
+ public ProfileDatabaseHelper getProfileDatabaseHelper() {
+ return getProfileProviderForTest().getDatabaseHelper();
+ }
+
@Override
public void onBegin() {
super.onBegin();
diff --git a/tests/src/com/android/providers/contacts/TestUtils.java b/tests/src/com/android/providers/contacts/TestUtils.java
index 5e88548..4d1f2d6 100644
--- a/tests/src/com/android/providers/contacts/TestUtils.java
+++ b/tests/src/com/android/providers/contacts/TestUtils.java
@@ -16,18 +16,29 @@
package com.android.providers.contacts;
+import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.os.FileUtils;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Profile;
+import android.provider.ContactsContract.RawContacts;
import android.support.annotation.Nullable;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
import android.util.Log;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+
import junit.framework.Assert;
+import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
public class TestUtils {
private static final String TAG = "ContactsTestUtils";
@@ -41,7 +52,7 @@
* we'll switch to file-based DBs, so we can call {@link #createDatabaseSnapshot}
* , pull the snapshot DBs and take a look at them.
*/
- public static final boolean ENABLE_DATABASE_SNAPSHOT = false; // DO NOT SUBMIT WITH TRUE.
+ public static final boolean ENABLE_DATABASE_SNAPSHOT = true; // DO NOT SUBMIT WITH TRUE.
private static final Object sDatabasePathLock = new Object();
private static File sDatabasePath = null;
@@ -59,14 +70,18 @@
} else {
Assert.assertTrue("Unable to create directory: " + path, path.mkdirs());
}
+ Log.i(TAG, "Test DB directory: " + path);
sDatabasePath = path;
}
+ final File ret;
if (name == null) {
- return sDatabasePath.getAbsolutePath();
+ ret = sDatabasePath;
} else {
- return new File(sDatabasePath, name).getAbsolutePath();
+ ret = new File(sDatabasePath, name);
+ Log.i(TAG, "Test DB file: " + ret);
}
+ return ret.getAbsolutePath();
}
}
@@ -100,7 +115,7 @@
Assert.assertTrue("Unable to create directory: " + toDir, toDir.mkdirs());
}
- Log.w(TAG, "Copying database files into '" + toDir + "'...");
+ Log.w(TAG, "Copying database files from '" + fromDir + "' into '" + toDir + "'...");
for (File file : fromDir.listFiles()) {
try {
@@ -140,20 +155,20 @@
* Writes the content of a cursor to the log.
*/
public static final void dumpCursor(Cursor c) {
- final String TAG = "contacts";
-
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < c.getColumnCount(); i++) {
- if (sb.length() > 0) sb.append("|");
+ if (i > 0) sb.append("|");
sb.append(c.getColumnName(i));
}
Log.i(TAG, sb.toString());
+ final int pos = c.getPosition();
+
c.moveToPosition(-1);
while (c.moveToNext()) {
sb.setLength(0);
for (int i = 0; i < c.getColumnCount(); i++) {
- if (sb.length() > 0) sb.append("|");
+ if (i > 0) sb.append("|");
if (c.getType(i) == Cursor.FIELD_TYPE_BLOB) {
byte[] blob = c.getBlob(i);
@@ -166,6 +181,29 @@
}
Log.i(TAG, sb.toString());
}
+
+ c.moveToPosition(pos);
+ }
+
+ public static void dumpTable(SQLiteDatabase db, String name) {
+ Log.i(TAG, "Dumping table: " + name);
+ try (Cursor c = db.rawQuery(String.format("SELECT * FROM %s", name), null)) {
+ dumpCursor(c);
+ }
+ }
+
+ public static void dumpUri(Context context, Uri uri) {
+ Log.i(TAG, "Dumping URI: " + uri);
+ try (Cursor c = context.getContentResolver().query(uri, null, null, null, null)) {
+ dumpCursor(c);
+ }
+ }
+
+ public static void dumpUri(ContentResolver resolver, Uri uri) {
+ Log.i(TAG, "Dumping URI: " + uri);
+ try (Cursor c = resolver.query(uri, null, null, null, null)) {
+ dumpCursor(c);
+ }
}
/**
@@ -183,4 +221,57 @@
return "[Failed to write to file: " + e.getMessage() + "]";
}
}
+
+ public static Uri insertRawContact(
+ ContentResolver resolver, ContactsDatabaseHelper dbh, ContentValues values) {
+ return insertRawContact(RawContacts.CONTENT_URI, resolver, dbh, values);
+ }
+
+ public static Uri insertProfileRawContact(
+ ContentResolver resolver, ContactsDatabaseHelper dbh, ContentValues values) {
+ return insertRawContact(Profile.CONTENT_RAW_CONTACTS_URI, resolver, dbh, values);
+ }
+
+ private static Uri insertRawContact(Uri tableUri,
+ ContentResolver resolver, ContactsDatabaseHelper dbh, ContentValues values) {
+ final SQLiteDatabase db = dbh.getWritableDatabase();
+
+ final Uri rowUri = resolver.insert(tableUri, values);
+ Long timesContacted = values.getAsLong(RawContacts.LR_TIMES_CONTACTED);
+ if (timesContacted != null) {
+ // TIMES_CONTACTED is no longer modifiable via resolver, so we update the DB directly.
+ final long rid = Long.parseLong(rowUri.getLastPathSegment());
+
+ final String[] args = {String.valueOf(rid)};
+
+ db.update(Tables.RAW_CONTACTS,
+ cv(RawContacts.RAW_TIMES_CONTACTED, (long) timesContacted),
+ "_id=?", args);
+
+ // Then propagate it to contacts too.
+ db.execSQL("UPDATE " + Tables.CONTACTS
+ + " SET " + Contacts.RAW_TIMES_CONTACTED + " = ("
+ + " SELECT sum(" + RawContacts.RAW_TIMES_CONTACTED + ") FROM "
+ + Tables.RAW_CONTACTS + " AS r "
+ + " WHERE " + Tables.CONTACTS + "._id = r." + RawContacts.CONTACT_ID
+ + " GROUP BY r." + RawContacts.CONTACT_ID + ")");
+ }
+ return rowUri;
+ }
+
+ public static void executeSqlFromAssetFile(
+ Context context, SQLiteDatabase db, String assetName) {
+ try (InputStream input = context.getAssets().open(assetName);) {
+ BufferedReader r = new BufferedReader(new InputStreamReader(input));
+ String query;
+ while ((query = r.readLine()) != null) {
+ if (query.trim().length() == 0 || query.startsWith("--")) {
+ continue;
+ }
+ db.execSQL(query);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
}
diff --git a/tests/src/com/android/providers/contacts/sqlite/SqlCheckerTest.java b/tests/src/com/android/providers/contacts/sqlite/SqlCheckerTest.java
index a0a8622..ee2b5be 100644
--- a/tests/src/com/android/providers/contacts/sqlite/SqlCheckerTest.java
+++ b/tests/src/com/android/providers/contacts/sqlite/SqlCheckerTest.java
@@ -193,6 +193,11 @@
checkEnsureNoInvalidTokens(true, "a b ';' c");
checkEnsureNoInvalidTokens(true, "a b /*;*/ c");
+
+ checkEnsureNoInvalidTokens(false, "a b x_ c");
+ checkEnsureNoInvalidTokens(false, "a b [X_OK] c");
+ checkEnsureNoInvalidTokens(true, "a b 'x_' c");
+ checkEnsureNoInvalidTokens(true, "a b /*x_*/ c");
}
private void checkEnsureSingleTokenOnly(boolean ok, String sql, String... tokens) {
diff --git a/tests/src/com/android/providers/contacts/util/MockClock.java b/tests/src/com/android/providers/contacts/util/MockClock.java
index dce06c9..03e8265 100644
--- a/tests/src/com/android/providers/contacts/util/MockClock.java
+++ b/tests/src/com/android/providers/contacts/util/MockClock.java
@@ -42,4 +42,8 @@
public void advance() {
mCurrentTimeMillis++;
}
+
+ public void advanceDay() {
+ mCurrentTimeMillis += 24 * 60 * 60;
+ }
}
diff --git a/tests2/src/com/android/providers/contacts/tests2/AllUrlTest.java b/tests2/src/com/android/providers/contacts/tests2/AllUrlTest.java
index 3bea2da..920243a 100644
--- a/tests2/src/com/android/providers/contacts/tests2/AllUrlTest.java
+++ b/tests2/src/com/android/providers/contacts/tests2/AllUrlTest.java
@@ -228,6 +228,12 @@
private void addFailure(String message) {
mFailures.add(message);
+ Log.e(TAG, "Failed: " + message);
+ if (mFailures.size() > 100) {
+ // Too many failures...
+ } else {
+ mFailures.add(message);
+ }
}
private void failIfFailed() {
@@ -282,19 +288,28 @@
selectionArgs, sortOrder)) {
c.moveToFirst();
}
+ } catch (Throwable th) {
+ addFailure("Query failed: URI=" + uri + " Message=" + th.getMessage());
+ }
+ try {
// With CancellationSignal.
try (Cursor c = getContext().getContentResolver().query(uri, projection, selection,
selectionArgs, sortOrder, new CancellationSignal())) {
c.moveToFirst();
}
+ } catch (Throwable th) {
+ addFailure("Query with cancel failed: URI=" + uri + " Message=" + th.getMessage());
+ }
+ try {
// With limit.
try (Cursor c = getContext().getContentResolver().query(
- uri.buildUpon().appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, "0").build(),
+ uri.buildUpon().appendQueryParameter(
+ ContactsContract.LIMIT_PARAM_KEY, "0").build(),
projection, selection, selectionArgs, sortOrder)) {
c.moveToFirst();
}
} catch (Throwable th) {
- addFailure("Query failed: URI=" + uri + " Message=" + th.getMessage());
+ addFailure("Query with limit failed: URI=" + uri + " Message=" + th.getMessage());
}
}
@@ -330,6 +345,20 @@
failIfFailed();
}
+ public void testNoHiddenColumns() {
+ for (String[] path : URIs) {
+ if (!supportsQuery(path)) continue;
+ final Uri uri = getUri(path);
+
+ for (String column : getColumns(uri)) {
+ if (column.toLowerCase().startsWith(ContactsContract.HIDDEN_COLUMN_PREFIX)) { // doesn't seem to be working
+ addFailure("Uri " + uri + " returned hidden column " + column);
+ }
+ }
+ }
+ failIfFailed();
+ }
+
/**
* Make sure all URLs are accessible with a projection.
*/
@@ -357,7 +386,8 @@
// These columns only show up when the projection contains certain columns.
projection = new String[]{"mode", column};
- } else if (u.startsWith("content://com.android.contacts/search_suggest_query")
+ } else if ((u.startsWith("content://com.android.contacts/search_suggest_query")
+ || u.startsWith("content://contacts/search_suggest_query"))
&& "suggest_intent_action".equals(column)) {
// Can't be included in the projection due to a bug in GlobalSearchSupport.
continue;
@@ -395,6 +425,26 @@
failIfFailed();
}
+// /**
+// * Make sure all URLs are accessible with a selection.
+// */
+// public void testSelectWithSelectionUsingColumns() {
+// for (String[] path : URIs) {
+// if (!supportsQuery(path)) continue;
+// final Uri uri = getUri(path);
+//
+// for (String column : getColumns(uri)) {
+// checkQueryExecutable(uri,
+// null, // projection
+// column + "=?", // selection
+// ARG1, // , // selection args
+// null // sort order
+// );
+// }
+// }
+// failIfFailed();
+// }
+
/**
* Make sure all URLs are accessible with an order-by.
*/
@@ -403,12 +453,14 @@
if (!supportsQuery(path)) continue;
final Uri uri = getUri(path);
- checkQueryExecutable(uri,
- null, // projection
- null, // selection
- null, // , // selection args
- getColumns(uri)[0] // sort order
- );
+ for (String column : getColumns(uri)) {
+ checkQueryExecutable(uri,
+ null, // projection
+ "1=2", // selection
+ null, // , // selection args
+ column // sort order
+ );
+ }
}
failIfFailed();
}