Fixing a concurrency issue in insert new contact

Bug: 2494578
Change-Id: Iee71342d053c440320e19521f4d4b6dff96e48c3
diff --git a/src/com/android/contacts/ui/EditContactActivity.java b/src/com/android/contacts/ui/EditContactActivity.java
index 01a9e12..d1e6328 100644
--- a/src/com/android/contacts/ui/EditContactActivity.java
+++ b/src/com/android/contacts/ui/EditContactActivity.java
@@ -181,6 +181,7 @@
 
     private static class QueryEntitiesTask extends
             WeakAsyncTask<Intent, Void, Void, EditContactActivity> {
+
         public QueryEntitiesTask(EditContactActivity target) {
             super(target);
         }
@@ -1153,246 +1154,144 @@
         return doSaveAction(SAVE_MODE_JOIN);
     }
 
-
-
-
-
-
-
-
     /**
      * Build dialog that handles adding a new {@link RawContacts} after the user
      * picks a specific {@link ContactsSource}.
      */
     private static class AddContactTask extends
-            WeakAsyncTask<Void, Void, AlertDialog.Builder, EditContactActivity> {
+            WeakAsyncTask<Void, Void, ArrayList<Account>, EditContactActivity> {
+
         public AddContactTask(EditContactActivity target) {
             super(target);
         }
 
         @Override
-        protected AlertDialog.Builder doInBackground(final EditContactActivity target,
+        protected ArrayList<Account> doInBackground(final EditContactActivity target,
                 Void... params) {
-            final Sources sources = Sources.getInstance(target);
-
-            // Wrap our context to inflate list items using correct theme
-            final Context dialogContext = new ContextThemeWrapper(target, android.R.style.Theme_Light);
-            final LayoutInflater dialogInflater = (LayoutInflater)dialogContext
-                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
-            final ArrayList<Account> writable = sources.getAccounts(true);
-
-            // No Accounts available.  Create a phone-local contact.
-            if (writable.isEmpty()) {
-                selectAccount(null);
-                return null;  // Don't show a dialog.
-            }
-
-            // In the common case of a single account being writable, auto-select
-            // it without showing a dialog.
-            if (writable.size() == 1) {
-                selectAccount(writable.get(0));
-                return null;  // Don't show a dialog.
-            }
-
-            final ArrayAdapter<Account> accountAdapter = new ArrayAdapter<Account>(target,
-                    android.R.layout.simple_list_item_2, writable) {
-                @Override
-                public View getView(int position, View convertView, ViewGroup parent) {
-                    if (convertView == null) {
-                        convertView = dialogInflater.inflate(android.R.layout.simple_list_item_2,
-                                parent, false);
-                    }
-
-                    // TODO: show icon along with title
-                    final TextView text1 = (TextView)convertView.findViewById(android.R.id.text1);
-                    final TextView text2 = (TextView)convertView.findViewById(android.R.id.text2);
-
-                    final Account account = this.getItem(position);
-                    final ContactsSource source = sources.getInflatedSource(account.type,
-                            ContactsSource.LEVEL_SUMMARY);
-
-                    text1.setText(account.name);
-                    text2.setText(source.getDisplayLabel(target));
-
-                    return convertView;
-                }
-            };
-
-            final DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() {
-                public void onClick(DialogInterface dialog, int which) {
-                    dialog.dismiss();
-
-                    // Create new contact based on selected source
-                    final Account account = accountAdapter.getItem(which);
-                    selectAccount(account);
-
-                    // Update the UI.
-                    EditContactActivity target = mTarget.get();
-                    if (target != null) {
-                        target.bindEditors();
-                    }
-                }
-            };
-
-            final DialogInterface.OnCancelListener cancelListener = new DialogInterface.OnCancelListener() {
-                public void onCancel(DialogInterface dialog) {
-                    // If nothing remains, close activity
-                    if (!target.hasValidState()) {
-                        target.finish();
-                    }
-                }
-            };
-
-            // TODO: when canceled and was single add, finish()
-            final AlertDialog.Builder builder = new AlertDialog.Builder(target);
-            builder.setTitle(R.string.dialog_new_contact_account);
-            builder.setSingleChoiceItems(accountAdapter, 0, clickListener);
-            builder.setOnCancelListener(cancelListener);
-            return builder;
-        }
-
-        /**
-         * Sets up EditContactActivity's mState for the account selected.
-         * Runs from a background thread.
-         *
-         * @param account may be null to signal a device-local contact should
-         *     be created.
-         */
-        private void selectAccount(Account account) {
-            EditContactActivity target = mTarget.get();
-            if (target == null) {
-                return;
-            }
-            final Sources sources = Sources.getInstance(target);
-            final ContentValues values = new ContentValues();
-            if (account != null) {
-                values.put(RawContacts.ACCOUNT_NAME, account.name);
-                values.put(RawContacts.ACCOUNT_TYPE, account.type);
-            } else {
-                values.putNull(RawContacts.ACCOUNT_NAME);
-                values.putNull(RawContacts.ACCOUNT_TYPE);
-            }
-
-            // Parse any values from incoming intent
-            final EntityDelta insert = new EntityDelta(ValuesDelta.fromAfter(values));
-            final ContactsSource source = sources.getInflatedSource(
-                account != null ? account.type : null,
-                ContactsSource.LEVEL_CONSTRAINTS);
-            final Bundle extras = target.getIntent().getExtras();
-            EntityModifier.parseExtras(target, source, insert, extras);
-
-            // Ensure we have some default fields
-            EntityModifier.ensureKindExists(insert, source, Phone.CONTENT_ITEM_TYPE);
-            EntityModifier.ensureKindExists(insert, source, Email.CONTENT_ITEM_TYPE);
-
-            // Create "My Contacts" membership for Google contacts
-            // TODO: move this off into "templates" for each given source
-            if (GoogleSource.ACCOUNT_TYPE.equals(source.accountType)) {
-                GoogleSource.attemptMyContactsMembership(insert, target);
-            }
-
-	    // TODO: no synchronization here on target.mState.  This
-	    // runs in the background thread, but it's accessed from
-	    // multiple thread, including the UI thread.
-            if (target.mState == null) {
-                // Create state if none exists yet
-                target.mState = EntitySet.fromSingle(insert);
-            } else {
-                // Add contact onto end of existing state
-                target.mState.add(insert);
-            }
+            return Sources.getInstance(target).getAccounts(true);
         }
 
         @Override
-        protected void onPostExecute(EditContactActivity target, AlertDialog.Builder result) {
-            if (result != null) {
-                // Note: null is returned when no dialog is to be
-                // shown (no multiple accounts to select between)
-                target.showAndManageDialog(result.create());
-            } else {
-                // Account was auto-selected on the background thread,
-                // but we need to update the UI still in the
-                // now-current UI thread.
-                target.bindEditors();
-            }
+        protected void onPostExecute(final EditContactActivity target, ArrayList<Account> accounts) {
+            target.selectAccountAndCreateContact(accounts);
         }
     }
 
+    public void selectAccountAndCreateContact(ArrayList<Account> accounts) {
+        // No Accounts available.  Create a phone-local contact.
+        if (accounts.isEmpty()) {
+            createContact(null);
+            return;  // Don't show a dialog.
+        }
 
-
-    private Dialog createDeleteDialog() {
-        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
-        builder.setTitle(R.string.deleteConfirmation_title);
-        builder.setIcon(android.R.drawable.ic_dialog_alert);
-        builder.setMessage(R.string.deleteConfirmation);
-        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
-            public void onClick(DialogInterface dialog, int which) {
-                // Mark all raw contacts for deletion
-                for (EntityDelta delta : mState) {
-                    delta.markDeleted();
-                }
-
-                // Save the deletes
-                doSaveAction(SAVE_MODE_DEFAULT);
-                finish();
-            }
-        });
-        builder.setNegativeButton(android.R.string.cancel, null);
-        builder.setCancelable(false);
-        return builder.create();
-    }
-
-    /**
-     * Create dialog for selecting primary display name.
-     */
-    private Dialog createNameDialog() {
-        // Build set of all available display names
-        final ArrayList<ValuesDelta> allNames = Lists.newArrayList();
-        for (EntityDelta entity : mState) {
-            final ArrayList<ValuesDelta> displayNames = entity
-                    .getMimeEntries(StructuredName.CONTENT_ITEM_TYPE);
-            allNames.addAll(displayNames);
+        // In the common case of a single account being writable, auto-select
+        // it without showing a dialog.
+        if (accounts.size() == 1) {
+            createContact(accounts.get(0));
+            return;  // Don't show a dialog.
         }
 
         // Wrap our context to inflate list items using correct theme
         final Context dialogContext = new ContextThemeWrapper(this, android.R.style.Theme_Light);
-        final LayoutInflater dialogInflater = this.getLayoutInflater()
-                .cloneInContext(dialogContext);
+        final LayoutInflater dialogInflater =
+            (LayoutInflater)dialogContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 
-        final ListAdapter nameAdapter = new ArrayAdapter<ValuesDelta>(this,
-                android.R.layout.simple_list_item_1, allNames) {
+        final Sources sources = Sources.getInstance(this);
+
+        final ArrayAdapter<Account> accountAdapter = new ArrayAdapter<Account>(this,
+                android.R.layout.simple_list_item_2, accounts) {
             @Override
             public View getView(int position, View convertView, ViewGroup parent) {
                 if (convertView == null) {
-                    convertView = dialogInflater.inflate(android.R.layout.simple_list_item_1,
+                    convertView = dialogInflater.inflate(android.R.layout.simple_list_item_2,
                             parent, false);
                 }
 
-                final ValuesDelta structuredName = this.getItem(position);
-                final String displayName = structuredName.getAsString(StructuredName.DISPLAY_NAME);
+                // TODO: show icon along with title
+                final TextView text1 = (TextView)convertView.findViewById(android.R.id.text1);
+                final TextView text2 = (TextView)convertView.findViewById(android.R.id.text2);
 
-                ((TextView)convertView).setText(displayName);
+                final Account account = this.getItem(position);
+                final ContactsSource source = sources.getInflatedSource(account.type,
+                        ContactsSource.LEVEL_SUMMARY);
+
+                text1.setText(account.name);
+                text2.setText(source.getDisplayLabel(EditContactActivity.this));
 
                 return convertView;
             }
         };
 
-        final DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() {
+        final DialogInterface.OnClickListener clickListener =
+                new DialogInterface.OnClickListener() {
             public void onClick(DialogInterface dialog, int which) {
                 dialog.dismiss();
 
-                // User picked display name, so make super-primary
-                final ValuesDelta structuredName = allNames.get(which);
-                structuredName.put(Data.IS_PRIMARY, 1);
-                structuredName.put(Data.IS_SUPER_PRIMARY, 1);
+                // Create new contact based on selected source
+                final Account account = accountAdapter.getItem(which);
+                createContact(account);
+            }
+        };
+
+        final DialogInterface.OnCancelListener cancelListener =
+                new DialogInterface.OnCancelListener() {
+            public void onCancel(DialogInterface dialog) {
+                // If nothing remains, close activity
+                if (!hasValidState()) {
+                    finish();
+                }
             }
         };
 
         final AlertDialog.Builder builder = new AlertDialog.Builder(this);
-        builder.setTitle(R.string.dialog_primary_name);
-        builder.setSingleChoiceItems(nameAdapter, 0, clickListener);
-        return builder.create();
+        builder.setTitle(R.string.dialog_new_contact_account);
+        builder.setSingleChoiceItems(accountAdapter, 0, clickListener);
+        builder.setOnCancelListener(cancelListener);
+        showAndManageDialog(builder.create());
+    }
+
+    /**
+     * @param account may be null to signal a device-local contact should
+     *     be created.
+     */
+    private void createContact(Account account) {
+        final Sources sources = Sources.getInstance(this);
+        final ContentValues values = new ContentValues();
+        if (account != null) {
+            values.put(RawContacts.ACCOUNT_NAME, account.name);
+            values.put(RawContacts.ACCOUNT_TYPE, account.type);
+        } else {
+            values.putNull(RawContacts.ACCOUNT_NAME);
+            values.putNull(RawContacts.ACCOUNT_TYPE);
+        }
+
+        // Parse any values from incoming intent
+        EntityDelta insert = new EntityDelta(ValuesDelta.fromAfter(values));
+        final ContactsSource source = sources.getInflatedSource(
+            account != null ? account.type : null,
+            ContactsSource.LEVEL_CONSTRAINTS);
+        final Bundle extras = getIntent().getExtras();
+        EntityModifier.parseExtras(this, source, insert, extras);
+
+        // Ensure we have some default fields
+        EntityModifier.ensureKindExists(insert, source, Phone.CONTENT_ITEM_TYPE);
+        EntityModifier.ensureKindExists(insert, source, Email.CONTENT_ITEM_TYPE);
+
+        // Create "My Contacts" membership for Google contacts
+        // TODO: move this off into "templates" for each given source
+        if (GoogleSource.ACCOUNT_TYPE.equals(source.accountType)) {
+            GoogleSource.attemptMyContactsMembership(insert, this);
+        }
+
+        if (mState == null) {
+            // Create state if none exists yet
+            mState = EntitySet.fromSingle(insert);
+        } else {
+            // Add contact onto end of existing state
+            mState.add(insert);
+        }
+
+        bindEditors();
     }
 
     /**