summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Shrauner <shrauner@google.com>2013-02-27 14:53:41 -0800
committerJay Shrauner <shrauner@google.com>2013-03-04 17:16:37 -0800
commit2d2e22626b698b2484026ae18eca3c2c6340f2d1 (patch)
treeea6320a5cda746895ad992bdde4150d6d5fbfd6f
parent21cfa6019b9fb82c23edf978d27904757207d9b0 (diff)
downloadpackages_providers_ContactsProvider-2d2e22626b698b2484026ae18eca3c2c6340f2d1.zip
packages_providers_ContactsProvider-2d2e22626b698b2484026ae18eca3c2c6340f2d1.tar.gz
packages_providers_ContactsProvider-2d2e22626b698b2484026ae18eca3c2c6340f2d1.tar.bz2
Auto-update ContactsDB on ICU version change
Save current ICU version into the ContactsDB and rebuild locale specific data whenever the version changes. Bug: Change-Id: Id5fd3e178558dc903b522b2655c75e6aa353f6e5
-rw-r--r--src/com/android/providers/contacts/ContactsDatabaseHelper.java111
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java57
-rw-r--r--src/com/android/providers/contacts/ContactsUpgradeReceiver.java33
-rw-r--r--src/com/android/providers/contacts/FastScrollingIndexCache.java26
-rw-r--r--tests/src/com/android/providers/contacts/FastScrollingIndexCacheTest.java5
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*"));