aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/providers/contacts/ContactsDatabaseHelper.java189
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java164
-rw-r--r--src/com/android/providers/contacts/LegacyApiSupport.java3
3 files changed, 222 insertions, 134 deletions
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 7f4188d2..82024d59 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -143,9 +143,10 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
* 1300-1399 P
* 1400-1499 Q
* 1500-1599 S
+ * 1600-1699 T
* </pre>
*/
- static final int DATABASE_VERSION = 1501;
+ static final int DATABASE_VERSION = 1600;
private static final int MINIMUM_SUPPORTED_VERSION = 700;
@VisibleForTesting
@@ -224,37 +225,21 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
+ ")";
// NOTE: This requires late binding of GroupMembership MIME-type
- // TODO Consolidate settings and accounts
public static final String RAW_CONTACTS_JOIN_SETTINGS_DATA_GROUPS = Tables.RAW_CONTACTS
+ " JOIN " + Tables.ACCOUNTS + " ON ("
+ RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" + AccountsColumns.CONCRETE_ID
+ ")"
- + "LEFT OUTER JOIN " + Tables.SETTINGS + " ON ("
- + AccountsColumns.CONCRETE_ACCOUNT_NAME + "="
- + SettingsColumns.CONCRETE_ACCOUNT_NAME + " AND "
- + AccountsColumns.CONCRETE_ACCOUNT_TYPE + "="
- + SettingsColumns.CONCRETE_ACCOUNT_TYPE + " AND "
- + "((" + AccountsColumns.CONCRETE_DATA_SET + " IS NULL AND "
- + SettingsColumns.CONCRETE_DATA_SET + " IS NULL) OR ("
- + AccountsColumns.CONCRETE_DATA_SET + "="
- + SettingsColumns.CONCRETE_DATA_SET + "))) "
+ "LEFT OUTER JOIN data ON (data.mimetype_id=? AND "
+ "data.raw_contact_id = raw_contacts._id) "
+ "LEFT OUTER JOIN groups ON (groups._id = data." + GroupMembership.GROUP_ROW_ID
+ ")";
// NOTE: This requires late binding of GroupMembership MIME-type
- // TODO Add missing DATA_SET join -- or just consolidate settings and accounts
- public static final String SETTINGS_JOIN_RAW_CONTACTS_DATA_MIMETYPES_CONTACTS = "settings "
+ public static final String SETTINGS_JOIN_RAW_CONTACTS_DATA_MIMETYPES_CONTACTS = "accounts "
+ "LEFT OUTER JOIN raw_contacts ON ("
- + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=(SELECT "
+ + RawContactsColumns.CONCRETE_ACCOUNT_ID + "="
+ AccountsColumns.CONCRETE_ID
- + " FROM " + Tables.ACCOUNTS
- + " WHERE "
- + "(" + AccountsColumns.CONCRETE_ACCOUNT_NAME
- + "=" + SettingsColumns.CONCRETE_ACCOUNT_NAME + ") AND "
- + "(" + AccountsColumns.CONCRETE_ACCOUNT_TYPE
- + "=" + SettingsColumns.CONCRETE_ACCOUNT_TYPE + ")))"
+ + ")"
+ "LEFT OUTER JOIN data ON (data.mimetype_id=? AND "
+ "data.raw_contact_id = raw_contacts._id) "
+ "LEFT OUTER JOIN contacts ON (raw_contacts.contact_id = contacts._id)";
@@ -340,6 +325,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
public static final String ENTITIES = "view_entities";
public static final String RAW_ENTITIES = "view_raw_entities";
public static final String GROUPS = "view_groups";
+ public static final String SETTINGS = "view_settings";
/** The data_usage_stat table with the low-res columns. */
public static final String DATA_USAGE_LR = "view_data_usage";
@@ -370,8 +356,8 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
public interface Clauses {
final String HAVING_NO_GROUPS = "COUNT(" + DataColumns.CONCRETE_GROUP_ID + ") == 0";
- final String GROUP_BY_ACCOUNT_CONTACT_ID = SettingsColumns.CONCRETE_ACCOUNT_NAME + ","
- + SettingsColumns.CONCRETE_ACCOUNT_TYPE + "," + RawContacts.CONTACT_ID;
+ final String GROUP_BY_ACCOUNT_CONTACT_ID = AccountsColumns.CONCRETE_ID + ","
+ + RawContacts.CONTACT_ID;
String LOCAL_ACCOUNT_ID =
"(SELECT " + AccountsColumns._ID +
@@ -381,9 +367,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
AccountsColumns.ACCOUNT_TYPE + " IS NULL AND " +
AccountsColumns.DATA_SET + " IS NULL)";
- final String RAW_CONTACT_IS_LOCAL = RawContactsColumns.CONCRETE_ACCOUNT_ID
- + "=" + LOCAL_ACCOUNT_ID;
-
final String ZERO_GROUP_MEMBERSHIPS = "COUNT(" + GroupsColumns.CONCRETE_ID + ")=0";
final String OUTER_RAW_CONTACTS = "outer_raw_contacts";
@@ -393,8 +376,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
"SELECT " +
"MAX((SELECT (CASE WHEN " +
"(CASE" +
- " WHEN " + RAW_CONTACT_IS_LOCAL +
- " THEN 1 " +
" WHEN " + ZERO_GROUP_MEMBERSHIPS +
" THEN " + Settings.UNGROUPED_VISIBLE +
" ELSE MAX(" + Groups.GROUP_VISIBLE + ")" +
@@ -417,6 +398,17 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
"EXISTS (SELECT _id FROM " + Tables.DEFAULT_DIRECTORY
+ " WHERE " + Tables.CONTACTS +"." + Contacts._ID
+ "=" + Tables.DEFAULT_DIRECTORY +"." + Contacts._ID + ")";
+
+ // Settings are in the accounts table and should only be deletable if there are no
+ // raw contacts or groups remaining in the account.
+ public static final String DELETABLE_SETTINGS =
+ "NOT EXISTS (SELECT 1 FROM " + Tables.RAW_CONTACTS
+ + " WHERE " + RawContactsColumns.ACCOUNT_ID + "="
+ + ViewSettingsColumns.CONCRETE_ACCOUNT_ID
+ + " UNION SELECT 1 FROM " + Tables.GROUPS
+ + " WHERE " + GroupsColumns.ACCOUNT_ID + "="
+ + ViewSettingsColumns.CONCRETE_ACCOUNT_ID
+ + ")";
}
public interface ContactsColumns {
@@ -559,12 +551,8 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
public static final String ACCOUNT_ID = "account_id";
public static final String CONCRETE_ACCOUNT_ID = Tables.GROUPS + "." + ACCOUNT_ID;
- }
- public interface ViewGroupsColumns {
- String CONCRETE_ACCOUNT_NAME = Views.GROUPS + "." + Groups.ACCOUNT_NAME;
- String CONCRETE_ACCOUNT_TYPE = Views.GROUPS + "." + Groups.ACCOUNT_TYPE;
- String CONCRETE_DATA_SET = Views.GROUPS + "." + Groups.DATA_SET;
+ public static final String CONCRETE_SHOULD_SYNC = Tables.GROUPS + "." + Groups.SHOULD_SYNC;
}
public interface ActivitiesColumns {
@@ -611,13 +599,9 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
public static final String CLUSTER = "cluster";
}
- public interface SettingsColumns {
- public static final String CONCRETE_ACCOUNT_NAME = Tables.SETTINGS + "."
- + Settings.ACCOUNT_NAME;
- public static final String CONCRETE_ACCOUNT_TYPE = Tables.SETTINGS + "."
- + Settings.ACCOUNT_TYPE;
- public static final String CONCRETE_DATA_SET = Tables.SETTINGS + "."
- + Settings.DATA_SET;
+ public interface ViewSettingsColumns {
+ public static final String ACCOUNT_ID = "account_id";
+ public static final String CONCRETE_ACCOUNT_ID = Views.SETTINGS + "." + ACCOUNT_ID;
}
public interface PresenceColumns {
@@ -703,10 +687,13 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
String DATA_SET = RawContacts.DATA_SET;
String SIM_SLOT_INDEX = "sim_slot_index";
String SIM_EF_TYPE = "sim_ef_type";
+ String UNGROUPED_VISIBLE = Settings.UNGROUPED_VISIBLE;
+ String SHOULD_SYNC = Settings.SHOULD_SYNC;
String CONCRETE_ACCOUNT_NAME = Tables.ACCOUNTS + "." + ACCOUNT_NAME;
String CONCRETE_ACCOUNT_TYPE = Tables.ACCOUNTS + "." + ACCOUNT_TYPE;
String CONCRETE_DATA_SET = Tables.ACCOUNTS + "." + DATA_SET;
+
}
public interface DirectoryColumns {
@@ -1224,8 +1211,9 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
AccountsColumns.ACCOUNT_TYPE + " TEXT, " +
AccountsColumns.DATA_SET + " TEXT, " +
AccountsColumns.SIM_SLOT_INDEX + " INTEGER, " +
- AccountsColumns.SIM_EF_TYPE + " INTEGER" +
- ");");
+ AccountsColumns.SIM_EF_TYPE + " INTEGER, " +
+ AccountsColumns.UNGROUPED_VISIBLE + " INTEGER NOT NULL DEFAULT 0," +
+ AccountsColumns.SHOULD_SYNC + " INTEGER NOT NULL DEFAULT 1" + ");");
// 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
@@ -1551,14 +1539,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
AggregationExceptions.RAW_CONTACT_ID1 +
");");
- db.execSQL("CREATE TABLE IF NOT EXISTS " + Tables.SETTINGS + " (" +
- Settings.ACCOUNT_NAME + " STRING NOT NULL," +
- Settings.ACCOUNT_TYPE + " STRING NOT NULL," +
- Settings.DATA_SET + " STRING," +
- Settings.UNGROUPED_VISIBLE + " INTEGER NOT NULL DEFAULT 0," +
- Settings.SHOULD_SYNC + " INTEGER NOT NULL DEFAULT 1" +
- ");");
-
db.execSQL("CREATE TABLE " + Tables.VISIBLE_CONTACTS + " (" +
Contacts._ID + " INTEGER PRIMARY KEY" +
");");
@@ -1607,6 +1587,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
// When adding new tables, be sure to also add size-estimates in updateSqliteStats
createContactsViews(db);
createGroupsView(db);
+ createSettingsView(db);
createContactsTriggers(db);
createContactsIndexes(db, false /* we build stats table later */);
createPresenceTables(db);
@@ -1776,14 +1757,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
+ " WHERE " + Groups._ID + "=OLD." + Groups._ID + ";"
+ " END");
- // Update DEFAULT_FILTER table per AUTO_ADD column update, see upgradeToVersion411.
- final String insertContactsWithoutAccount = (
- " INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY +
- " SELECT " + RawContacts.CONTACT_ID +
- " FROM " + Tables.RAW_CONTACTS +
- " WHERE " + RawContactsColumns.CONCRETE_ACCOUNT_ID +
- "=" + Clauses.LOCAL_ACCOUNT_ID + ";");
-
final String insertContactsWithAccountNoDefaultGroup = (
" INSERT OR IGNORE INTO " + Tables.DEFAULT_DIRECTORY +
" SELECT " + RawContacts.CONTACT_ID +
@@ -1818,7 +1791,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
+ " AFTER UPDATE OF " + Groups.AUTO_ADD + " ON " + Tables.GROUPS
+ " BEGIN "
+ " DELETE FROM " + Tables.DEFAULT_DIRECTORY + ";"
- + insertContactsWithoutAccount
+ insertContactsWithAccountNoDefaultGroup
+ insertContactsWithAccountDefaultGroup
+ " END");
@@ -2252,7 +2224,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
+ Groups.SYSTEM_ID + ","
+ Groups.DELETED + ","
+ Groups.GROUP_VISIBLE + ","
- + Groups.SHOULD_SYNC + ","
+ + GroupsColumns.CONCRETE_SHOULD_SYNC + " AS " + Groups.SHOULD_SYNC + ","
+ Groups.AUTO_ADD + ","
+ Groups.FAVORITES + ","
+ Groups.GROUP_IS_READ_ONLY + ","
@@ -2274,6 +2246,48 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
db.execSQL("CREATE VIEW " + Views.GROUPS + " AS " + groupsSelect);
}
+ private void createSettingsView(SQLiteDatabase db) {
+ db.execSQL("DROP TRIGGER IF EXISTS " + Views.SETTINGS + "_update;");
+ db.execSQL("DROP TRIGGER IF EXISTS " + Tables.ACCOUNTS + "_insert_local_account ");
+ db.execSQL("DROP VIEW IF EXISTS " + Views.SETTINGS + ";");
+
+ String settingsColumns = AccountsColumns.CONCRETE_ID
+ + " AS " + ViewSettingsColumns.ACCOUNT_ID + ","
+ + AccountsColumns.CONCRETE_ACCOUNT_NAME + " AS " + Settings.ACCOUNT_NAME + ","
+ + AccountsColumns.CONCRETE_ACCOUNT_TYPE + " AS " + Settings.ACCOUNT_TYPE + ","
+ + AccountsColumns.CONCRETE_DATA_SET + " AS " + Settings.DATA_SET + ","
+ + Settings.UNGROUPED_VISIBLE + ","
+ + Settings.SHOULD_SYNC;
+
+ String settingsSelect = "SELECT " + settingsColumns + " FROM " + Tables.ACCOUNTS;
+
+ db.execSQL("CREATE VIEW " + Views.SETTINGS + " AS " + settingsSelect);
+
+ // A trigger is used to update settings to prevent changing the other columns in the
+ // accounts table that are not settings related.
+ db.execSQL("CREATE TRIGGER " + Views.SETTINGS + "_update "
+ + "INSTEAD OF UPDATE ON " + Views.SETTINGS + " "
+ + "BEGIN UPDATE " + Tables.ACCOUNTS + " SET "
+ + AccountsColumns.UNGROUPED_VISIBLE + " = NEW."
+ + Settings.UNGROUPED_VISIBLE + ", "
+ + AccountsColumns.SHOULD_SYNC + " = NEW." + Settings.SHOULD_SYNC + " "
+ + "WHERE _id = OLD." + ViewSettingsColumns.ACCOUNT_ID + "; "
+ + "END;");
+ // Unlike other accounts ungrouped contacts in the local account are visible by default and
+ // it is not syncable.
+ db.execSQL("CREATE TRIGGER " + Tables.ACCOUNTS + "_insert_local_account "
+ + "AFTER INSERT ON " + Tables.ACCOUNTS + " "
+ + "WHEN NEW." + AccountsColumns.ACCOUNT_NAME + " IS NULL AND "
+ + "NEW." + AccountsColumns.ACCOUNT_TYPE + " IS NULL AND "
+ + "NEW." + AccountsColumns.DATA_SET + " IS NULL "
+ + "BEGIN UPDATE " + Tables.ACCOUNTS + " SET "
+ + Settings.UNGROUPED_VISIBLE + " = 1, "
+ + Settings.SHOULD_SYNC + " = 0 "
+ + "WHERE " + AccountsColumns._ID + " = NEW." + AccountsColumns._ID + "; "
+ + "END;"
+ );
+ }
+
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i(TAG, "ContactsProvider cannot proceed because downgrading your database is not " +
@@ -2594,12 +2608,19 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
oldVersion = 1501;
}
+ if (isUpgradeRequired(oldVersion, newVersion, 1600)) {
+ upgradeToVersion1600(db);
+ upgradeViewsAndTriggers = true;
+ oldVersion = 1600;
+ }
+
// We extracted "calls" and "voicemail_status" at this point, but we can't remove them here
// yet, until CallLogDatabaseHelper moves the data.
if (upgradeViewsAndTriggers) {
createContactsViews(db);
createGroupsView(db);
+ createSettingsView(db);
createContactsTriggers(db);
createContactsIndexes(db, false /* we build stats table later */);
upgradeLegacyApiSupport = true;
@@ -3375,6 +3396,46 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
}
}
+ private void upgradeToVersion1600(SQLiteDatabase db) {
+ db.execSQL("ALTER TABLE accounts ADD ungrouped_visible INTEGER NOT NULL DEFAULT 0;");
+ db.execSQL("ALTER TABLE accounts ADD should_sync INTEGER NOT NULL DEFAULT 1;");
+
+ ContentValues values = new ContentValues();
+ // Copy over the existing settings rows.
+ try (Cursor cursor = db.query("settings", new String[]{
+ Settings.ACCOUNT_NAME, Settings.ACCOUNT_TYPE, Settings.DATA_SET,
+ Settings.UNGROUPED_VISIBLE, Settings.SHOULD_SYNC
+ }, null, null, null, null, null)) {
+ String[] selectionArgs = new String[3];
+ while (cursor.moveToNext()) {
+ DatabaseUtils.cursorRowToContentValues(cursor, values);
+ selectionArgs[0] = values.getAsString(Settings.ACCOUNT_NAME);
+ selectionArgs[1] = values.getAsString(Settings.ACCOUNT_TYPE);
+ selectionArgs[2] = values.getAsString(Settings.DATA_SET);
+ if (values.getAsString(Settings.DATA_SET) != null) {
+ db.update("accounts", values,
+ "account_name = ? AND account_type = ? AND data_set = ?",
+ selectionArgs);
+ } else {
+ db.update("accounts", values,
+ "account_name = ? AND account_type = ? AND data_set IS ?",
+ selectionArgs);
+ }
+ }
+ }
+
+ db.execSQL("DROP TABLE settings;");
+
+ // If the local account exists update it's settings so that ungrouped contacts are
+ // visible by default for the local account.
+ values.clear();
+ values.put("ungrouped_visible", true);
+ values.put("should_sync", false);
+ db.update("accounts", values,
+ "account_name IS NULL AND account_type IS NULL AND data_set IS NULL", null);
+ }
+
+
/**
* 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
@@ -3632,8 +3693,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
// Tiny tables
updateIndexStats(db, Tables.AGGREGATION_EXCEPTIONS,
null, "10");
- updateIndexStats(db, Tables.SETTINGS,
- null, "10");
updateIndexStats(db, Tables.PACKAGES,
null, "0");
updateIndexStats(db, Tables.DIRECTORIES,
@@ -3708,7 +3767,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
db.execSQL("DELETE FROM " + Tables.NAME_LOOKUP + ";");
db.execSQL("DELETE FROM " + Tables.GROUPS + ";");
db.execSQL("DELETE FROM " + Tables.AGGREGATION_EXCEPTIONS + ";");
- db.execSQL("DELETE FROM " + Tables.SETTINGS + ";");
db.execSQL("DELETE FROM " + Tables.DIRECTORIES + ";");
db.execSQL("DELETE FROM " + Tables.SEARCH_INDEX + ";");
db.execSQL("DELETE FROM " + Tables.DELETED_CONTACTS + ";");
@@ -3998,7 +4056,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
} finally {
insert.close();
}
-
return id;
}
@@ -4117,12 +4174,6 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper {
+ GroupsColumns.CONCRETE_ACCOUNT_ID +
" AND " + Groups.AUTO_ADD + " != 0" +
")" +
- ") OR EXISTS (" +
- "SELECT " + RawContacts._ID +
- " FROM " + Tables.RAW_CONTACTS +
- " WHERE " + RawContacts.CONTACT_ID + "=?1" +
- " AND " + RawContactsColumns.CONCRETE_ACCOUNT_ID + "=" +
- Clauses.LOCAL_ACCOUNT_ID +
")",
new String[] {
contactIdAsString,
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index befafc05..a3524d52 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -123,7 +123,6 @@ import android.util.Log;
import com.android.common.content.ProjectionMap;
import com.android.common.content.SyncStateContentProviderHelper;
import com.android.common.io.MoreCloseables;
-import com.android.i18n.phonenumbers.Phonenumber;
import com.android.internal.util.ArrayUtils;
import com.android.providers.contacts.ContactLookupKey.LookupKeySegment;
import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
@@ -146,12 +145,11 @@ import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Projections;
import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.SearchIndexColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.SettingsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.StreamItemPhotosColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.StreamItemsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.ContactsDatabaseHelper.ViewGroupsColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.ViewSettingsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Views;
import com.android.providers.contacts.SearchIndexManager.FtsQueryBuilder;
import com.android.providers.contacts.aggregation.AbstractContactAggregator;
@@ -246,6 +244,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
private static final int BACKGROUND_TASK_CLEANUP_PHOTOS = 10;
private static final int BACKGROUND_TASK_CLEAN_DELETE_LOG = 11;
private static final int BACKGROUND_TASK_RESCAN_DIRECTORY = 12;
+ private static final int BACKGROUND_TASK_CLEANUP_DANGLING_CONTACTS = 13;
protected static final int STATUS_NORMAL = 0;
protected static final int STATUS_UPGRADING = 1;
@@ -261,6 +260,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
/** Rate limit (in milliseconds) for photo cleanup. Do it at most once per day. */
private static final int PHOTO_CLEANUP_RATE_LIMIT = 24 * 60 * 60 * 1000;
+ /** Rate limit (in milliseconds) for dangling contacts cleanup. Do it at most once per day. */
+ private static final int DANGLING_CONTACTS_CLEANUP_RATE_LIMIT = 24 * 60 * 60 * 1000;
+
/** Maximum length of a phone number that can be inserted into the database */
private static final int PHONE_NUMBER_LENGTH_LIMIT = 1000;
@@ -1006,15 +1008,10 @@ public class ContactsProvider2 extends AbstractContactsProvider
+ " THEN 1"
+ " ELSE MIN(" + Groups.SHOULD_SYNC + ")"
+ " END)"
- + " FROM " + Views.GROUPS
- + " WHERE " + ViewGroupsColumns.CONCRETE_ACCOUNT_NAME + "="
- + SettingsColumns.CONCRETE_ACCOUNT_NAME
- + " AND " + ViewGroupsColumns.CONCRETE_ACCOUNT_TYPE + "="
- + SettingsColumns.CONCRETE_ACCOUNT_TYPE
- + " AND ((" + ViewGroupsColumns.CONCRETE_DATA_SET + " IS NULL AND "
- + SettingsColumns.CONCRETE_DATA_SET + " IS NULL) OR ("
- + ViewGroupsColumns.CONCRETE_DATA_SET + "="
- + SettingsColumns.CONCRETE_DATA_SET + "))))=0"
+ + " FROM " + Tables.GROUPS
+ + " WHERE " + GroupsColumns.CONCRETE_ACCOUNT_ID + "="
+ + ViewSettingsColumns.CONCRETE_ACCOUNT_ID
+ + "))=0"
+ " THEN 1"
+ " ELSE 0"
+ " END)")
@@ -1460,6 +1457,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
private long mLastPhotoCleanup = 0;
+ private long mLastDanglingContactsCleanup = 0;
+
private FastScrollingIndexCache mFastScrollingIndexCache;
// Stats about FastScrollingIndex.
@@ -1551,6 +1550,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
scheduleBackgroundTask(BACKGROUND_TASK_OPEN_WRITE_ACCESS);
scheduleBackgroundTask(BACKGROUND_TASK_CLEANUP_PHOTOS);
scheduleBackgroundTask(BACKGROUND_TASK_CLEAN_DELETE_LOG);
+ scheduleBackgroundTask(BACKGROUND_TASK_CLEANUP_DANGLING_CONTACTS);
ContactsPackageMonitor.start(getContext());
@@ -1756,6 +1756,17 @@ public class ContactsProvider2 extends AbstractContactsProvider
DeletedContactsTableUtil.deleteOldLogs(db);
break;
}
+
+ case BACKGROUND_TASK_CLEANUP_DANGLING_CONTACTS: {
+ // Check rate limit.
+ long now = System.currentTimeMillis();
+ if (now - mLastDanglingContactsCleanup > DANGLING_CONTACTS_CLEANUP_RATE_LIMIT) {
+ mLastDanglingContactsCleanup = now;
+
+ cleanupDanglingContacts();
+ }
+ break;
+ }
}
}
@@ -1983,6 +1994,28 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
}
+
+ @VisibleForTesting
+ protected void cleanupDanglingContacts() {
+ // Dangling contacts are the contacts whose _id doesn't have a raw_contact_id linked with.
+ String danglingContactsSelection =
+ Contacts._ID
+ + " NOT IN (SELECT "
+ + RawContacts.CONTACT_ID
+ + " FROM "
+ + Tables.RAW_CONTACTS
+ + " WHERE "
+ + RawContacts.DELETED
+ + " = 0)";
+ int danglingContactsCount =
+ mDbHelper
+ .get()
+ .getWritableDatabase()
+ .delete(Tables.CONTACTS, danglingContactsSelection,
+ /* selectionArgs= */null);
+ Log.v(TAG, danglingContactsCount + " Dangling Contacts have been cleaned up.");
+ }
+
@Override
public ContactsDatabaseHelper newDatabaseHelper(final Context context) {
return ContactsDatabaseHelper.getInstance(context);
@@ -2683,9 +2716,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
case SETTINGS: {
- id = insertSettings(values);
mSyncToNetwork |= !callerIsSyncAdapter;
- break;
+ // Settings rows are referenced by the account instead of their ID.
+ return insertSettings(uri, values);
}
case STATUS_UPDATES:
@@ -3243,55 +3276,35 @@ public class ContactsProvider2 extends AbstractContactsProvider
return groupId;
}
- private long insertSettings(ContentValues values) {
- // Before inserting, ensure that no settings record already exists for the
- // values being inserted (this used to be enforced by a primary key, but that no
- // longer works with the nullable data_set field added).
- String accountName = values.getAsString(Settings.ACCOUNT_NAME);
- String accountType = values.getAsString(Settings.ACCOUNT_TYPE);
- String dataSet = values.getAsString(Settings.DATA_SET);
- Uri.Builder settingsUri = Settings.CONTENT_URI.buildUpon();
- if (accountName != null) {
- settingsUri.appendQueryParameter(Settings.ACCOUNT_NAME, accountName);
- }
- if (accountType != null) {
- settingsUri.appendQueryParameter(Settings.ACCOUNT_TYPE, accountType);
- }
- if (dataSet != null) {
- settingsUri.appendQueryParameter(Settings.DATA_SET, dataSet);
- }
- Cursor c = queryLocal(settingsUri.build(), null, null, null, null, 0, null);
- try {
- if (c.getCount() > 0) {
- // If a record was found, replace it with the new values.
- String selection = null;
- String[] selectionArgs = null;
- if (accountName != null && accountType != null) {
- selection = Settings.ACCOUNT_NAME + "=? AND " + Settings.ACCOUNT_TYPE + "=?";
- if (dataSet == null) {
- selection += " AND " + Settings.DATA_SET + " IS NULL";
- selectionArgs = new String[] {accountName, accountType};
- } else {
- selection += " AND " + Settings.DATA_SET + "=?";
- selectionArgs = new String[] {accountName, accountType, dataSet};
- }
- }
- return updateSettings(values, selection, selectionArgs);
- }
- } finally {
- c.close();
- }
+ private Uri insertSettings(Uri uri, ContentValues values) {
+ final AccountWithDataSet account = resolveAccountWithDataSet(uri, values);
+ // Note that the following check means the local account settings cannot be created with
+ // an insert because resolveAccountWithDataSet returns null for it. However, the settings
+ // for it can be updated once it is created automatically by a raw contact or group insert.
+ if (account == null) {
+ return null;
+ }
+ final ContactsDatabaseHelper dbHelper = mDbHelper.get();
final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
- // If we didn't find a duplicate, we're fine to insert.
- final long id = db.insert(Tables.SETTINGS, null, values);
+ long accountId = dbHelper.getOrCreateAccountIdInTransaction(account);
+ mSelectionArgs1[0] = String.valueOf(accountId);
+
+ int count = db.update(Views.SETTINGS, values,
+ ViewSettingsColumns.ACCOUNT_ID + "= ?", mSelectionArgs1);
if (values.containsKey(Settings.UNGROUPED_VISIBLE)) {
mVisibleTouched = true;
}
- return id;
+ Uri.Builder builder = Settings.CONTENT_URI.buildUpon()
+ .appendQueryParameter(Settings.ACCOUNT_NAME, account.getAccountName())
+ .appendQueryParameter(Settings.ACCOUNT_TYPE, account.getAccountType());
+ if (account.getDataSet() != null) {
+ builder.appendQueryParameter(Settings.DATA_SET, account.getDataSet());
+ }
+ return builder.build();
}
/**
@@ -3718,7 +3731,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
case SETTINGS: {
mSyncToNetwork |= !callerIsSyncAdapter;
- return deleteSettings(appendAccountToSelection(uri, selection), selectionArgs);
+ return deleteSettings(appendAccountIdToSelection(uri, selection), selectionArgs);
}
case STATUS_UPDATES:
@@ -3795,9 +3808,21 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
}
- private int deleteSettings(String selection, String[] selectionArgs) {
+ private int deleteSettings(String initialSelection, String[] selectionArgs) {
final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
- final int count = db.delete(Tables.SETTINGS, selection, selectionArgs);
+
+ int count = 0;
+ final String selection = DbQueryUtils.concatenateClauses(
+ initialSelection, Clauses.DELETABLE_SETTINGS);
+ try (Cursor cursor = db.query(Views.SETTINGS,
+ new String[] { ViewSettingsColumns.ACCOUNT_ID },
+ selection, selectionArgs, null, null, null)) {
+ while (cursor.moveToNext()) {
+ mSelectionArgs1[0] = cursor.getString(0);
+ db.delete(Tables.ACCOUNTS, AccountsColumns._ID + "=?", mSelectionArgs1);
+ count++;
+ }
+ }
mVisibleTouched = true;
return count;
}
@@ -4466,7 +4491,20 @@ public class ContactsProvider2 extends AbstractContactsProvider
private int updateSettings(ContentValues values, String selection, String[] selectionArgs) {
final SQLiteDatabase db = mDbHelper.get().getWritableDatabase();
- final int count = db.update(Tables.SETTINGS, values, selection, selectionArgs);
+
+ int count = 0;
+ // We have to query for the count because the update is using a trigger and triggers
+ // don't return a count of modified rows.
+ try (Cursor cursor = db.query(Views.SETTINGS,
+ new String[] { "COUNT(*)" },
+ selection, selectionArgs, null, null, null)) {
+ if (cursor.moveToFirst()) {
+ count = cursor.getInt(0);
+ }
+ }
+ if (count > 0) {
+ db.update(Views.SETTINGS, values, selection, selectionArgs);
+ }
if (values.containsKey(Settings.UNGROUPED_VISIBLE)) {
mVisibleTouched = true;
}
@@ -5247,9 +5285,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
}
- // Second, remove stale rows from Tables.SETTINGS and Tables.DIRECTORIES
- removeStaleAccountRows(
- Tables.SETTINGS, Settings.ACCOUNT_NAME, Settings.ACCOUNT_TYPE, systemAccounts);
+ // Second, remove stale rows from Tables.DIRECTORIES
removeStaleAccountRows(Tables.DIRECTORIES, Directory.ACCOUNT_NAME,
Directory.ACCOUNT_TYPE, systemAccounts);
@@ -6831,9 +6867,9 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
case SETTINGS: {
- qb.setTables(Tables.SETTINGS);
+ qb.setTables(Views.SETTINGS);
qb.setProjectionMap(sSettingsProjectionMap);
- appendAccountFromParameter(qb, uri);
+ appendAccountIdFromParameter(qb, uri);
// When requesting specific columns, this query requires
// late-binding of the GroupMembership MIME-type.
diff --git a/src/com/android/providers/contacts/LegacyApiSupport.java b/src/com/android/providers/contacts/LegacyApiSupport.java
index c111cf39..deff98a3 100644
--- a/src/com/android/providers/contacts/LegacyApiSupport.java
+++ b/src/com/android/providers/contacts/LegacyApiSupport.java
@@ -65,6 +65,7 @@ import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
+import com.android.providers.contacts.ContactsDatabaseHelper.Views;
import java.util.Locale;
@@ -1246,7 +1247,7 @@ public class LegacyApiSupport {
+ ContactsContract.Settings.ACCOUNT_NAME + ","
+ ContactsContract.Settings.ACCOUNT_TYPE + ","
+ ContactsContract.Settings.SHOULD_SYNC +
- " FROM " + Tables.SETTINGS + " LEFT OUTER JOIN " + LegacyTables.SETTINGS +
+ " FROM " + Views.SETTINGS + " LEFT OUTER JOIN " + LegacyTables.SETTINGS +
" ON (" + ContactsContract.Settings.ACCOUNT_NAME + "="
+ android.provider.Contacts.Settings._SYNC_ACCOUNT +
" AND " + ContactsContract.Settings.ACCOUNT_TYPE + "="