diff options
5 files changed, 185 insertions, 47 deletions
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java index 6cd0a70..260d05f 100644 --- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java +++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java @@ -84,6 +84,8 @@ import java.util.HashMap; import java.util.Locale; import java.util.Set; +import libcore.icu.ICU; + /** * Database helper for contacts. Designed as a singleton to make sure that all * {@link android.content.ContentProvider} users get the same reference. @@ -107,7 +109,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { * 700-799 Jelly Bean * </pre> */ - static final int DATABASE_VERSION = 707; + static final int DATABASE_VERSION = 708; private static final String DATABASE_NAME = "contacts2.db"; private static final String DATABASE_PRESENCE = "presence_db"; @@ -706,6 +708,8 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { String DIRECTORY_SCAN_COMPLETE = "directoryScanComplete"; String AGGREGATION_ALGORITHM = "aggregation_v2"; String KNOWN_ACCOUNTS = "known_accounts"; + String ICU_VERSION = "icu_version"; + String LOCALE = "locale"; } /** In-memory cache of previously found MIME-type mappings */ @@ -1986,6 +1990,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { boolean upgradeSearchIndex = false; boolean rescanDirectories = false; boolean rebuildSqliteStats = false; + boolean upgradeLocaleSpecificData = false; if (oldVersion == 99) { upgradeViewsAndTriggers = true; @@ -2442,6 +2447,13 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { oldVersion = 707; } + if (oldVersion < 708) { + // Sort keys, phonebook labels and buckets, and search keys have + // changed so force a rebuild. + upgradeLocaleSpecificData = true; + oldVersion = 708; + } + if (upgradeViewsAndTriggers) { createContactsViews(db); createGroupsView(db); @@ -2455,14 +2467,21 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { LegacyApiSupport.createViews(db); } + if (upgradeLocaleSpecificData) { + upgradeLocaleData(db, false /* we build stats table later */); + // Name lookups are rebuilt as part of the full locale rebuild + upgradeNameLookup = false; + upgradeSearchIndex = true; + rebuildSqliteStats = true; + } + if (upgradeNameLookup) { rebuildNameLookup(db, false /* we build stats table later */); rebuildSqliteStats = true; } if (upgradeSearchIndex) { - createSearchIndexTable(db, false /* we build stats table later */); - setProperty(db, SearchIndexManager.PROPERTY_SEARCH_INDEX_VERSION, "0"); + rebuildSearchIndex(db, false /* we build stats table later */); rebuildSqliteStats = true; } @@ -3027,25 +3046,81 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { createContactsIndexes(db, rebuildSqliteStats); } + protected void rebuildSearchIndex() { + rebuildSearchIndex(getWritableDatabase(), true); + } + + private void rebuildSearchIndex(SQLiteDatabase db, boolean rebuildSqliteStats) { + createSearchIndexTable(db, rebuildSqliteStats); + setProperty(db, SearchIndexManager.PROPERTY_SEARCH_INDEX_VERSION, "0"); + } + /** - * Regenerates all locale-sensitive data: nickname_lookup, name_lookup and sort keys. + * Checks whether the current ICU code version matches that used to build + * the locale specific data in the ContactsDB. */ - public void setLocale(ContactsProvider2 provider, Locale locale) { - Log.i(TAG, "Switching to locale " + locale); + public boolean needsToUpdateLocaleData(Locale locale) { + final String dbLocale = getProperty(DbProperties.LOCALE, ""); + if (!dbLocale.equals(locale.toString())) { + return true; + } + final String curICUVersion = ICU.getIcuVersion(); + final String dbICUVersion = getProperty(DbProperties.ICU_VERSION, + "(unknown)"); + if (!curICUVersion.equals(dbICUVersion)) { + Log.i(TAG, "ICU version has changed. Current version is " + + curICUVersion + "; DB was built with " + dbICUVersion); + return true; + } + return false; + } + + private void upgradeLocaleData(SQLiteDatabase db, boolean rebuildSqliteStats) { + final Locale locale = Locale.getDefault(); + Log.i(TAG, "Upgrading locale data for " + locale + + " (ICU v" + ICU.getIcuVersion() + ")"); + final long start = SystemClock.elapsedRealtime(); + initializeCache(db); + rebuildLocaleData(db, locale, rebuildSqliteStats); + Log.i(TAG, "Locale update completed in " + (SystemClock.elapsedRealtime() - start) + "ms"); + } + + private void rebuildLocaleData(SQLiteDatabase db, Locale locale, + boolean rebuildSqliteStats) { + db.execSQL("DROP INDEX raw_contact_sort_key1_index"); + db.execSQL("DROP INDEX raw_contact_sort_key2_index"); + db.execSQL("DROP INDEX IF EXISTS name_lookup_index"); + + loadNicknameLookupTable(db); + insertNameLookup(db); + rebuildSortKeys(db); + createContactsIndexes(db, rebuildSqliteStats); + + FastScrollingIndexCache.getInstance(mContext).invalidate(); + // Update the ICU version used to generate the locale derived data + // so we can tell when we need to rebuild with new ICU versions. + setProperty(db, DbProperties.ICU_VERSION, ICU.getIcuVersion()); + setProperty(db, DbProperties.LOCALE, locale.toString()); + } + + /** + * Regenerates all locale-sensitive data if needed: + * nickname_lookup, name_lookup and sort keys. Invalidates the fast + * scrolling index cache. + */ + public void setLocale(Locale locale) { + if (!needsToUpdateLocaleData(locale)) { + return; + } + Log.i(TAG, "Switching to locale " + locale + + " (ICU v" + ICU.getIcuVersion() + ")"); final long start = SystemClock.elapsedRealtime(); SQLiteDatabase db = getWritableDatabase(); db.setLocale(locale); db.beginTransaction(); try { - db.execSQL("DROP INDEX raw_contact_sort_key1_index"); - db.execSQL("DROP INDEX raw_contact_sort_key2_index"); - db.execSQL("DROP INDEX IF EXISTS name_lookup_index"); - - loadNicknameLookupTable(db); - insertNameLookup(db); - rebuildSortKeys(db, provider); - createContactsIndexes(db, true); + rebuildLocaleData(db, locale, true); db.setTransactionSuccessful(); } finally { db.endTransaction(); @@ -3057,7 +3132,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { /** * Regenerates sort keys for all contacts. */ - private void rebuildSortKeys(SQLiteDatabase db, ContactsProvider2 provider) { + private void rebuildSortKeys(SQLiteDatabase db) { Cursor cursor = db.query(Tables.RAW_CONTACTS, new String[]{RawContacts._ID}, null, null, null, null, null); try { @@ -4776,7 +4851,11 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { * Returns the value from the {@link Tables#PROPERTIES} table. */ public String getProperty(String key, String defaultValue) { - Cursor cursor = getReadableDatabase().query(Tables.PROPERTIES, + return getProperty(getReadableDatabase(), key, defaultValue); + } + + public String getProperty(SQLiteDatabase db, String key, String defaultValue) { + Cursor cursor = db.query(Tables.PROPERTIES, new String[]{PropertiesColumns.PROPERTY_VALUE}, PropertiesColumns.PROPERTY_KEY + "=?", new String[]{key}, null, null, null); diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java index 1d88298..b9eb4dc 100644 --- a/src/com/android/providers/contacts/ContactsProvider2.java +++ b/src/com/android/providers/contacts/ContactsProvider2.java @@ -1390,7 +1390,7 @@ public class ContactsProvider2 extends AbstractContactsProvider StrictMode.setThreadPolicy( new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build()); - mFastScrollingIndexCache = new FastScrollingIndexCache(getContext()); + mFastScrollingIndexCache = FastScrollingIndexCache.getInstance(getContext()); mContactsHelper = getDatabaseHelper(getContext()); mDbHelper.set(mContactsHelper); @@ -1623,6 +1623,26 @@ public class ContactsProvider2 extends AbstractContactsProvider scheduleBackgroundTask(BACKGROUND_TASK_CHANGE_LOCALE); } + private static boolean needsToUpdateLocaleData(SharedPreferences prefs, + Locale locale,ContactsDatabaseHelper contactsHelper, + ProfileDatabaseHelper profileHelper) { + final String providerLocale = prefs.getString(PREF_LOCALE, null); + + // If locale matches that of the provider, and neither DB needs + // updating, there's nothing to do. A DB might require updating + // as a result of a system upgrade. + if (!locale.toString().equals(providerLocale)) { + Log.i(TAG, "Locale has changed from " + providerLocale + + " to " + locale.toString()); + return true; + } + if (contactsHelper.needsToUpdateLocaleData(locale) || + profileHelper.needsToUpdateLocaleData(locale)) { + return true; + } + return false; + } + /** * Verifies that the contacts database is properly configured for the current locale. * If not, changes the database locale to the current locale using an asynchronous task. @@ -1637,23 +1657,44 @@ public class ContactsProvider2 extends AbstractContactsProvider return; } - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); - final String providerLocale = prefs.getString(PREF_LOCALE, null); final Locale currentLocale = mCurrentLocale; - if (currentLocale.toString().equals(providerLocale)) { + final SharedPreferences prefs = + PreferenceManager.getDefaultSharedPreferences(getContext()); + if (!needsToUpdateLocaleData(prefs, currentLocale, + mContactsHelper, mProfileHelper)) { return; } int providerStatus = mProviderStatus; setProviderStatus(ProviderStatus.STATUS_CHANGING_LOCALE); - mContactsHelper.setLocale(this, currentLocale); - mProfileHelper.setLocale(this, currentLocale); + mContactsHelper.setLocale(currentLocale); + mProfileHelper.setLocale(currentLocale); mSearchIndexManager.updateIndex(true); - prefs.edit().putString(PREF_LOCALE, currentLocale.toString()).apply(); - invalidateFastScrollingIndexCache(); + prefs.edit().putString(PREF_LOCALE, currentLocale.toString()).commit(); setProviderStatus(providerStatus); } + // Static update routine for use by ContactsUpgradeReceiver during startup. + // This clears the search index and marks it to be rebuilt, but doesn't + // actually rebuild it. That is done later by + // BACKGROUND_TASK_UPDATE_SEARCH_INDEX. + protected static void updateLocaleOffline(Context context, + ContactsDatabaseHelper contactsHelper, + ProfileDatabaseHelper profileHelper) { + final Locale currentLocale = Locale.getDefault(); + final SharedPreferences prefs = + PreferenceManager.getDefaultSharedPreferences(context); + if (!needsToUpdateLocaleData(prefs, currentLocale, + contactsHelper, profileHelper)) { + return; + } + + contactsHelper.setLocale(currentLocale); + profileHelper.setLocale(currentLocale); + contactsHelper.rebuildSearchIndex(); + prefs.edit().putString(PREF_LOCALE, currentLocale.toString()).commit(); + } + /** * Reinitializes the provider for a new locale. */ diff --git a/src/com/android/providers/contacts/ContactsUpgradeReceiver.java b/src/com/android/providers/contacts/ContactsUpgradeReceiver.java index e259ffe..ba8acb8 100644 --- a/src/com/android/providers/contacts/ContactsUpgradeReceiver.java +++ b/src/com/android/providers/contacts/ContactsUpgradeReceiver.java @@ -26,6 +26,8 @@ import android.content.pm.PackageManager; import android.os.RemoteException; import android.util.Log; +import libcore.icu.ICU; + /** * This will be launched during system boot, after the core system has * been brought up but before any non-persistent processes have been @@ -39,6 +41,7 @@ import android.util.Log; public class ContactsUpgradeReceiver extends BroadcastReceiver { static final String TAG = "ContactsUpgradeReceiver"; static final String PREF_DB_VERSION = "db_version"; + static final String PREF_ICU_VERSION = "icu_version"; @Override public void onReceive(Context context, Intent intent) { @@ -50,33 +53,37 @@ public class ContactsUpgradeReceiver extends BroadcastReceiver { // Lookup the last known database version SharedPreferences prefs = context.getSharedPreferences(TAG, Context.MODE_PRIVATE); - int prefVersion = prefs.getInt(PREF_DB_VERSION, 0); + int prefDbVersion = prefs.getInt(PREF_DB_VERSION, 0); + final String curIcuVersion = ICU.getIcuVersion(); + final String prefIcuVersion = prefs.getString(PREF_ICU_VERSION, ""); // If the version is old go ahead and attempt to create or upgrade the database. - if (prefVersion != ContactsDatabaseHelper.DATABASE_VERSION) { + if (prefDbVersion != ContactsDatabaseHelper.DATABASE_VERSION || + !prefIcuVersion.equals(curIcuVersion)) { // Store the current version so this receiver isn't run again until the database // version number changes. This is intentionally done even before the upgrade path // is attempted to be conservative. If the upgrade fails for some reason and we // crash and burn we don't want to get into a loop doing so. - prefs.edit().putInt( - PREF_DB_VERSION, ContactsDatabaseHelper.DATABASE_VERSION).commit(); + SharedPreferences.Editor editor = prefs.edit(); + editor.putInt(PREF_DB_VERSION, ContactsDatabaseHelper.DATABASE_VERSION); + editor.putString(PREF_ICU_VERSION, curIcuVersion); + editor.commit(); // Ask for a reference to the database to force the helper to either // create the database or open it up, performing any necessary upgrades // in the process. ContactsDatabaseHelper helper = ContactsDatabaseHelper.getInstance(context); - if (context.getDatabasePath(helper.getDatabaseName()).exists()) { - Log.i(TAG, "Creating or opening contacts database"); - try { - ActivityManagerNative.getDefault().showBootMessage( - context.getText(R.string.upgrade_msg), true); - } catch (RemoteException e) { - } - helper.getWritableDatabase(); + ProfileDatabaseHelper profileHelper = ProfileDatabaseHelper.getInstance(context); + Log.i(TAG, "Creating or opening contacts database"); + try { + ActivityManagerNative.getDefault().showBootMessage( + context.getText(R.string.upgrade_msg), true); + } catch (RemoteException e) { } - ProfileDatabaseHelper profileHelper = ProfileDatabaseHelper.getInstance(context); + helper.getWritableDatabase(); profileHelper.getWritableDatabase(); + ContactsProvider2.updateLocaleOffline(context, helper, profileHelper); // Log the total time taken for the receiver to perform the operation EventLogTags.writeContactsUpgradeReceiver(System.currentTimeMillis() - startTime); diff --git a/src/com/android/providers/contacts/FastScrollingIndexCache.java b/src/com/android/providers/contacts/FastScrollingIndexCache.java index f07a855..9b3908e 100644 --- a/src/com/android/providers/contacts/FastScrollingIndexCache.java +++ b/src/com/android/providers/contacts/FastScrollingIndexCache.java @@ -85,19 +85,29 @@ public class FastScrollingIndexCache { */ private final Map<String, String> mCache = Maps.newHashMap(); - public FastScrollingIndexCache(Context context) { - this(PreferenceManager.getDefaultSharedPreferences(context)); + private static FastScrollingIndexCache sSingleton; - // At this point, the SharedPreferences might just have been generated and may still be - // loading from the file, in which case loading from the preferences would be blocked. - // To avoid that, we load lazily. + public static FastScrollingIndexCache getInstance(Context context) { + return getInstance(PreferenceManager.getDefaultSharedPreferences(context)); } - @VisibleForTesting - FastScrollingIndexCache(SharedPreferences prefs) { + public static synchronized FastScrollingIndexCache getInstance( + SharedPreferences prefs) { + if (sSingleton == null) { + sSingleton = new FastScrollingIndexCache(prefs); + } + return sSingleton; + } + + private FastScrollingIndexCache(SharedPreferences prefs) { mPrefs = prefs; } + @VisibleForTesting + protected static synchronized void releaseInstance() { + sSingleton = null; + } + /** * Append a {@link String} to a {@link StringBuilder}. * @@ -238,7 +248,7 @@ public class FastScrollingIndexCache { public void invalidate() { synchronized (mCache) { - mPrefs.edit().remove(PREFERENCE_KEY).apply(); + mPrefs.edit().remove(PREFERENCE_KEY).commit(); mCache.clear(); mPreferenceLoaded = true; diff --git a/tests/src/com/android/providers/contacts/FastScrollingIndexCacheTest.java b/tests/src/com/android/providers/contacts/FastScrollingIndexCacheTest.java index 281834f..de01d9e 100644 --- a/tests/src/com/android/providers/contacts/FastScrollingIndexCacheTest.java +++ b/tests/src/com/android/providers/contacts/FastScrollingIndexCacheTest.java @@ -54,7 +54,7 @@ public class FastScrollingIndexCacheTest extends AndroidTestCase { super.setUp(); mPrefs = new MockSharedPreferences(); - mCache = new FastScrollingIndexCache(mPrefs); + mCache = FastScrollingIndexCache.getInstance(mPrefs); } private void assertBundle(String[] expectedTitles, int[] expectedCounts, Bundle actual) { @@ -141,7 +141,8 @@ public class FastScrollingIndexCacheTest extends AndroidTestCase { // Now, create a new cache instance (with the same shared preferences) // It should restore the cache content from the preferences... - FastScrollingIndexCache cache2 = new FastScrollingIndexCache(mPrefs); + FastScrollingIndexCache.releaseInstance(); + FastScrollingIndexCache cache2 = FastScrollingIndexCache.getInstance(mPrefs); assertBundle(TITLES_0, COUNTS_0, cache2.get(null, null, null, null, null)); assertBundle(TITLES_1, COUNTS_1, cache2.get(URI_A, "*s*", PROJECTION_0, "*so*", "*ce*")); assertBundle(TITLES_2, COUNTS_2, cache2.get(URI_A, "*s*", PROJECTION_1, "*so*", "*ce*")); |