diff options
Diffstat (limited to 'src')
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 + "=" |