Merge "Import revised translations."
diff --git a/src/com/android/contacts/ContactsApplication.java b/src/com/android/contacts/ContactsApplication.java
index df12cf8..1e5c14e 100644
--- a/src/com/android/contacts/ContactsApplication.java
+++ b/src/com/android/contacts/ContactsApplication.java
@@ -17,7 +17,14 @@
package com.android.contacts;
import android.app.Application;
+import android.content.Context;
import android.os.StrictMode;
+import android.preference.PreferenceManager;
+
+import java.util.Locale;
+
+import com.android.contacts.model.AccountTypes;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
public final class ContactsApplication extends Application {
@@ -25,6 +32,12 @@
public void onCreate() {
super.onCreate();
+ // Priming caches to placate the StrictMode police
+ Context context = getApplicationContext();
+ PreferenceManager.getDefaultSharedPreferences(context);
+ PhoneNumberUtil.getInstance().getAsYouTypeFormatter(Locale.getDefault().getCountry());
+ AccountTypes.getInstance(context);
+
StrictMode.setThreadPolicy(
new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
}
diff --git a/src/com/android/contacts/list/ContactListFilter.java b/src/com/android/contacts/list/ContactListFilter.java
index dcaf608..2a2caf7 100644
--- a/src/com/android/contacts/list/ContactListFilter.java
+++ b/src/com/android/contacts/list/ContactListFilter.java
@@ -97,12 +97,12 @@
@Override
public int compareTo(ContactListFilter another) {
- int res = accountType.compareTo(another.accountType);
+ int res = accountName.compareTo(another.accountName);
if (res != 0) {
return res;
}
- res = accountName.compareTo(another.accountName);
+ res = accountType.compareTo(another.accountType);
if (res != 0) {
return res;
}
diff --git a/src/com/android/contacts/model/AccountTypes.java b/src/com/android/contacts/model/AccountTypes.java
index 9b282fb..92ffba2 100644
--- a/src/com/android/contacts/model/AccountTypes.java
+++ b/src/com/android/contacts/model/AccountTypes.java
@@ -32,22 +32,29 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SyncAdapterType;
+import android.content.SyncStatusObserver;
import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.util.Log;
-import java.lang.ref.SoftReference;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.concurrent.CountDownLatch;
/**
* Singleton holder for all parsed {@link AccountType} available on the
* system, typically filled through {@link PackageManager} queries.
*/
-public class AccountTypes extends BroadcastReceiver implements OnAccountsUpdateListener {
+public class AccountTypes extends BroadcastReceiver
+ implements OnAccountsUpdateListener, SyncStatusObserver {
private static final String TAG = "AccountTypes";
private Context mContext;
@@ -56,28 +63,52 @@
private AccountType mFallbackSource = null;
+ private ArrayList<Account> mAccounts = Lists.newArrayList();
+ private ArrayList<Account> mWritableAccounts = Lists.newArrayList();
private HashMap<String, AccountType> mSources = Maps.newHashMap();
private HashSet<String> mKnownPackages = Sets.newHashSet();
- private static SoftReference<AccountTypes> sInstance = null;
+ private static final int MESSAGE_LOAD_DATA = 0;
+ private static final int MESSAGE_PROCESS_BROADCAST_INTENT = 1;
+ private static final int MESSAGE_SYNC_STATUS_CHANGED = 2;
+
+ private HandlerThread mListenerThread;
+ private Handler mListenerHandler;
+
+ /* A latch that ensures that asynchronous initialization completes before data is used */
+ private CountDownLatch mInitializationLatch;
+
+ private static AccountTypes sInstance = null;
+
+ private static final Comparator<Account> ACCOUNT_COMPARATOR = new Comparator<Account>() {
+
+ @Override
+ public int compare(Account account1, Account account2) {
+ int diff = account1.name.compareTo(account2.name);
+ if (diff != 0) {
+ return diff;
+ }
+ return account1.type.compareTo(account2.type);
+ }
+ };
/**
* Requests the singleton instance of {@link AccountTypes} with data bound from
* the available authenticators. This method can safely be called from the UI thread.
*/
public static synchronized AccountTypes getInstance(Context context) {
- AccountTypes sources = sInstance == null ? null : sInstance.get();
- if (sources == null) {
- sources = new AccountTypes(context);
- sInstance = new SoftReference<AccountTypes>(sources);
+ if (sInstance == null) {
+ sInstance = new AccountTypes(context);
}
- return sources;
+ return sInstance;
}
/**
* Internal constructor that only performs initial parsing.
*/
private AccountTypes(Context context) {
+ mInitializationLatch = new CountDownLatch(1);
+
mContext = context;
mApplicationContext = context.getApplicationContext();
mAccountManager = AccountManager.get(mApplicationContext);
@@ -85,7 +116,24 @@
// Create fallback contacts source for on-phone contacts
mFallbackSource = new FallbackAccountType();
- queryAccounts();
+ mListenerThread = new HandlerThread("AccountChangeListener");
+ mListenerThread.start();
+ mListenerHandler = new Handler(mListenerThread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_LOAD_DATA:
+ loadAccountsInBackground();
+ break;
+ case MESSAGE_PROCESS_BROADCAST_INTENT:
+ processBroadcastIntent((Intent) msg.obj);
+ break;
+ case MESSAGE_SYNC_STATUS_CHANGED:
+ loadAccountsInBackground();
+ break;
+ }
+ }
+ };
// Request updates when packages or accounts change
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
@@ -103,7 +151,11 @@
filter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
mApplicationContext.registerReceiver(this, filter);
- mAccountManager.addOnAccountsUpdatedListener(this, null, false);
+ mAccountManager.addOnAccountsUpdatedListener(this, mListenerHandler, false);
+
+ ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, this);
+
+ mListenerHandler.sendEmptyMessage(MESSAGE_LOAD_DATA);
}
/** @hide exposed for unit tests */
@@ -118,9 +170,18 @@
mKnownPackages.add(source.resPackageName);
}
- /** {@inheritDoc} */
@Override
public void onReceive(Context context, Intent intent) {
+ Message msg = mListenerHandler.obtainMessage(MESSAGE_PROCESS_BROADCAST_INTENT, intent);
+ mListenerHandler.sendMessage(msg);
+ }
+
+ @Override
+ public void onStatusChanged(int which) {
+ mListenerHandler.sendEmptyMessage(MESSAGE_SYNC_STATUS_CHANGED);
+ }
+
+ public void processBroadcastIntent(Intent intent) {
final String action = intent.getAction();
if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
@@ -145,7 +206,7 @@
invalidateCache(packageName);
} else {
// Unknown source, so reload from scratch
- queryAccounts();
+ loadAccountsInBackground();
}
}
}
@@ -170,18 +231,40 @@
}
}
- /** {@inheritDoc} */
+ /* This notification will arrive on the background thread */
public void onAccountsUpdated(Account[] accounts) {
// Refresh to catch any changed accounts
- queryAccounts();
+ loadAccountsInBackground();
}
/**
- * Loads all {@link AuthenticatorDescription} known by the {@link AccountManager} on the system.
+ * Returns instantly if accounts and account types have already been loaded.
+ * Otherwise waits for the background thread to complete the loading.
*/
- protected synchronized void queryAccounts() {
+ void ensureAccountsLoaded() {
+ CountDownLatch latch = mInitializationLatch;
+ if (latch == null) {
+ return;
+ }
+ while (true) {
+ try {
+ latch.await();
+ return;
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ /**
+ * Loads account list and corresponding account types. Always called on a
+ * background thread.
+ */
+ protected void loadAccountsInBackground() {
mSources.clear();
mKnownPackages.clear();
+ mAccounts.clear();
+ mWritableAccounts.clear();
final AccountManager am = mAccountManager;
final IContentService cs = ContentResolver.getContentService();
@@ -223,6 +306,38 @@
} catch (RemoteException e) {
Log.w(TAG, "Problem loading accounts: " + e.toString());
}
+
+ Account[] accounts = mAccountManager.getAccounts();
+ for (Account account : accounts) {
+ boolean syncable = false;
+ try {
+ int isSyncable = cs.getIsSyncable(account, ContactsContract.AUTHORITY);
+ if (isSyncable > 0) {
+ syncable = true;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot obtain sync flag for account: " + account, e);
+ }
+
+ if (syncable) {
+ // Ensure we have details loaded for each account
+ final AccountType accountType = getAccountType(
+ account.type, AccountType.LEVEL_SUMMARY);
+ if (accountType != null) {
+ mAccounts.add(account);
+ if (!accountType.readOnly) {
+ mWritableAccounts.add(account);
+ }
+ }
+ }
+ }
+
+ Collections.sort(mAccounts, ACCOUNT_COMPARATOR);
+ Collections.sort(mWritableAccounts, ACCOUNT_COMPARATOR);
+ if (mInitializationLatch != null) {
+ mInitializationLatch.countDown();
+ mInitializationLatch = null;
+ }
}
/**
@@ -240,40 +355,11 @@
}
/**
- * Return list of all known, writable {@link AccountType}. AccountTypes
- * returned may require inflation before they can be used.
+ * Return list of all known, writable {@link Account}'s.
*/
public ArrayList<Account> getAccounts(boolean writableOnly) {
- final AccountManager am = mAccountManager;
- final Account[] accounts = am.getAccounts();
- final ArrayList<Account> matching = Lists.newArrayList();
- final IContentService cs = ContentResolver.getContentService();
-
- for (Account account : accounts) {
- boolean syncable = false;
- try {
- int isSyncable = cs.getIsSyncable(account, ContactsContract.AUTHORITY);
- if (isSyncable > 0) {
- syncable = true;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Cannot obtain sync flag for account: " + account, e);
- }
- // Log.v(TAG, String.format("found account (name: %s, type: %s, syncable: %b",
- // account.name, account.type, syncable));
- if (syncable) {
- // Ensure we have details loaded for each account
- final AccountType accountType = getInflatedSource(account.type,
- AccountType.LEVEL_SUMMARY);
- final boolean hasContacts = accountType != null;
- final boolean matchesWritable =
- (!writableOnly || (writableOnly && !accountType.readOnly));
- if (hasContacts && matchesWritable) {
- matching.add(account);
- }
- }
- }
- return matching;
+ ensureAccountsLoaded();
+ return writableOnly ? mWritableAccounts : mAccounts;
}
/**
@@ -285,6 +371,7 @@
*/
public DataKind getKindOrFallback(String accountType, String mimeType, Context context,
int inflateLevel) {
+ ensureAccountsLoaded();
DataKind kind = null;
// Try finding source and kind matching request
@@ -311,6 +398,11 @@
* Return {@link AccountType} for the given account type.
*/
public AccountType getInflatedSource(String accountType, int inflateLevel) {
+ ensureAccountsLoaded();
+ return getAccountType(accountType, inflateLevel);
+ }
+
+ AccountType getAccountType(String accountType, int inflateLevel) {
// Try finding specific source, otherwise use fallback
AccountType source = mSources.get(accountType);
if (source == null) source = mFallbackSource;