Use loader for accounts in editor account chooser

The list of accounts on the dialog now updates when they are added or
removed.

Test: manually verify that list of accounts updates when adding or
removing accounts

Bug 33627801

Change-Id: I3c92c80f0a93a865050b115b5b3d931eea80b2af
diff --git a/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
index 50e11f3..3531125 100644
--- a/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
@@ -18,8 +18,10 @@
 
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.app.LoaderManager;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.Loader;
 import android.os.Bundle;
 import android.provider.ContactsContract.Intents;
 import android.view.View;
@@ -33,10 +35,13 @@
 import com.android.contacts.R;
 import com.android.contacts.editor.ContactEditorUtils;
 import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.account.AccountInfo;
 import com.android.contacts.model.account.AccountWithDataSet;
+import com.android.contacts.model.account.AccountsLoader;
 import com.android.contacts.util.AccountsListAdapter;
 import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
 import com.android.contacts.util.ImplicitIntentsUtil;
+import com.google.common.util.concurrent.Futures;
 
 import java.util.List;
 
@@ -48,7 +53,8 @@
  * the new contact in. If the activity result doesn't contain intent data, then there is no
  * account for this contact.
  */
-public class ContactEditorAccountsChangedActivity extends Activity {
+public class ContactEditorAccountsChangedActivity extends Activity
+        implements LoaderManager.LoaderCallbacks<List<AccountInfo>> {
 
     private static final String TAG = ContactEditorAccountsChangedActivity.class.getSimpleName();
 
@@ -95,10 +101,30 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-
         mEditorUtils = ContactEditorUtils.create(this);
-        final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(this).
-                getAccounts(true);
+        getLoaderManager().initLoader(0, null, this);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == SUBACTIVITY_ADD_NEW_ACCOUNT) {
+            // If the user canceled the account setup process, then keep this activity visible to
+            // the user.
+            if (resultCode != RESULT_OK) {
+                return;
+            }
+            // Subactivity was successful, so pass the result back and finish the activity.
+            AccountWithDataSet account = mEditorUtils.getCreatedAccount(resultCode, data);
+            if (account == null) {
+                setResult(resultCode);
+                finish();
+                return;
+            }
+            saveAccountAndReturnResult(account);
+        }
+    }
+
+    private void updateDisplayedAccounts(List<AccountInfo> accounts) {
         final int numAccounts = accounts.size();
         if (numAccounts < 0) {
             throw new IllegalStateException("Cannot have a negative number of accounts");
@@ -123,7 +149,7 @@
                     AccountListFilter.ACCOUNTS_CONTACT_WRITABLE);
             accountListView.setAdapter(mAccountListAdapter);
             accountListView.setOnItemClickListener(mAccountListItemClickListener);
-        } else if (numAccounts == 1 && !accounts.get(0).isNullAccount()) {
+        } else if (numAccounts == 1 && !accounts.get(0).getAccount().isNullAccount()) {
             // If the user has 1 writable account we will just show the user a message with 2
             // possible action buttons.
             view = View.inflate(this,
@@ -133,9 +159,9 @@
             final Button leftButton = (Button) view.findViewById(R.id.left_button);
             final Button rightButton = (Button) view.findViewById(R.id.right_button);
 
-            final AccountWithDataSet account = accounts.get(0);
+            final AccountInfo accountInfo = accounts.get(0);
             textView.setText(getString(R.string.contact_editor_prompt_one_account,
-                    account.name));
+                    accountInfo.getNameLabel()));
 
             // This button allows the user to add a new account to the device and return to
             // this app afterwards.
@@ -148,7 +174,7 @@
             rightButton.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
-                    saveAccountAndReturnResult(account);
+                    saveAccountAndReturnResult(accountInfo.getAccount());
                 }
             });
         } else {
@@ -183,6 +209,9 @@
             rightButton.setOnClickListener(mAddAccountClickListener);
         }
 
+        if (mDialog != null && mDialog.isShowing()) {
+            mDialog.dismiss();
+        }
         mDialog = new AlertDialog.Builder(this)
                 .setView(view)
                 .setOnCancelListener(new DialogInterface.OnCancelListener() {
@@ -195,25 +224,6 @@
         mDialog.show();
     }
 
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        if (requestCode == SUBACTIVITY_ADD_NEW_ACCOUNT) {
-            // If the user canceled the account setup process, then keep this activity visible to
-            // the user.
-            if (resultCode != RESULT_OK) {
-                return;
-            }
-            // Subactivity was successful, so pass the result back and finish the activity.
-            AccountWithDataSet account = mEditorUtils.getCreatedAccount(resultCode, data);
-            if (account == null) {
-                setResult(resultCode);
-                finish();
-                return;
-            }
-            saveAccountAndReturnResult(account);
-        }
-    }
-
     private void saveAccountAndReturnResult(AccountWithDataSet account) {
         // Save this as the default account
         mEditorUtils.saveDefaultAccount(account);
@@ -224,4 +234,18 @@
         setResult(RESULT_OK, intent);
         finish();
     }
+
+    @Override
+    public Loader<List<AccountInfo>> onCreateLoader(int id, Bundle args) {
+        return new AccountsLoader(this, AccountTypeManager.writableFilter());
+    }
+
+    @Override
+    public void onLoadFinished(Loader<List<AccountInfo>> loader, List<AccountInfo> data) {
+        updateDisplayedAccounts(data);
+    }
+
+    @Override
+    public void onLoaderReset(Loader<List<AccountInfo>> loader) {
+    }
 }
diff --git a/src/com/android/contacts/model/AccountTypeManager.java b/src/com/android/contacts/model/AccountTypeManager.java
index 93ddd04..961fa63 100644
--- a/src/com/android/contacts/model/AccountTypeManager.java
+++ b/src/com/android/contacts/model/AccountTypeManager.java
@@ -429,7 +429,7 @@
         reloadAccountTypes();
     }
 
-    /* This notification will arrive on the background thread */
+    /* This notification will arrive on the UI thread */
     public void onAccountsUpdated(Account[] accounts) {
         onAccountsUpdatedInternal();
     }
diff --git a/src/com/android/contacts/model/account/AccountsLoader.java b/src/com/android/contacts/model/account/AccountsLoader.java
new file mode 100644
index 0000000..78f309b
--- /dev/null
+++ b/src/com/android/contacts/model/account/AccountsLoader.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.model.account;
+
+import android.content.Context;
+import android.content.IntentFilter;
+
+import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.util.concurrent.ListenableFutureLoader;
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.List;
+
+/**
+ * Loads the accounts from AccountTypeManager
+ */
+public class AccountsLoader extends ListenableFutureLoader<List<AccountInfo>> {
+    private final AccountTypeManager mAccountTypeManager;
+    private final Predicate<AccountInfo> mFilter;
+
+    public AccountsLoader(Context context) {
+        this(context, Predicates.<AccountInfo>alwaysTrue());
+    }
+
+    public AccountsLoader(Context context, Predicate<AccountInfo> filter) {
+        super(context, new IntentFilter(AccountTypeManager.BROADCAST_ACCOUNTS_CHANGED));
+        mAccountTypeManager = AccountTypeManager.getInstance(context);
+        mFilter = filter;
+    }
+
+    @Override
+    protected ListenableFuture<List<AccountInfo>> loadData() {
+        return mAccountTypeManager.filterAccountsAsync(mFilter);
+    }
+
+    @Override
+    protected boolean isSameData(List<AccountInfo> previous, List<AccountInfo> next) {
+        return Objects.equal(AccountInfo.extractAccounts(previous),
+                AccountInfo.extractAccounts(next));
+    }
+
+}
diff --git a/src/com/android/contacts/util/concurrent/ListenableFutureLoader.java b/src/com/android/contacts/util/concurrent/ListenableFutureLoader.java
index 8c90d87..441ca68 100644
--- a/src/com/android/contacts/util/concurrent/ListenableFutureLoader.java
+++ b/src/com/android/contacts/util/concurrent/ListenableFutureLoader.java
@@ -94,8 +94,10 @@
         Futures.addCallback(mFuture, new FutureCallback<D>() {
             @Override
             public void onSuccess(D result) {
+                if (mLoadedData == null || !isSameData(mLoadedData, result)) {
+                    deliverResult(result);
+                }
                 mLoadedData = result;
-                deliverResult(mLoadedData);
                 commitContentChanged();
             }
 
@@ -130,6 +132,21 @@
 
     protected abstract ListenableFuture<D> loadData();
 
+    /**
+     * Returns whether the newly loaded data is the same as the cached value
+     *
+     * <p>This allows subclasses to suppress delivering results when the data hasn't
+     * actually changed. By default it will always return false.
+     * </p>
+     */
+    protected boolean isSameData(D previousData, D newData) {
+        return false;
+    }
+
+    public final D getLoadedData() {
+        return mLoadedData;
+    }
+
     public class ForceLoadReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {