Evicting import/export into a class of its own

Also fixing some issues with dialogs on orientation change

To verify, please use Contact Intents / DEFAULT

TODO: need to add a unit test for this interaction

Change-Id: I3f00b16ee19b6b8522c4526e4bbef17b0afb2ed2
diff --git a/res/values/ids.xml b/res/values/ids.xml
index d71a8d2..309119f 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -55,6 +55,9 @@
     <!-- For ContactsListActivity -->
     <item type="id" name="dialog_delete_contact_confirmation"/>
 
+    <!-- For ImportExportInteraction -->
+    <item type="id" name="dialog_import_export_options"/>
+
     <!-- For ExportVCardActivity -->
     <item type="id" name="dialog_export_confirmation"/>
     <item type="id" name="dialog_exporting_vcard"/>
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 2c75051..73a9a1a 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -17,6 +17,7 @@
 package com.android.contacts;
 
 import com.android.contacts.interactions.ContactDeletionInteraction;
+import com.android.contacts.interactions.ImportExportInteraction;
 import com.android.contacts.interactions.PhoneNumberInteraction;
 import com.android.contacts.list.ContactBrowseListContextMenuAdapter;
 import com.android.contacts.list.ContactEntryListFragment;
@@ -31,45 +32,26 @@
 import com.android.contacts.list.PhoneNumberPickerFragment;
 import com.android.contacts.list.PostalAddressPickerFragment;
 import com.android.contacts.list.StrequentContactListFragment;
-import com.android.contacts.model.Sources;
 import com.android.contacts.ui.ContactsPreferencesActivity;
-import com.android.contacts.util.AccountSelectionUtil;
-import com.android.contacts.vcard.ExportVCardActivity;
 import com.android.contacts.widget.ContextMenuAdapter;
 import com.android.contacts.widget.SearchEditText;
 import com.android.contacts.widget.SearchEditText.OnFilterTextListener;
 
-import android.accounts.Account;
 import android.app.Activity;
-import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.FragmentTransaction;
 import android.content.ContentValues;
-import android.content.Context;
-import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.res.Resources;
-import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.Contacts;
 import android.provider.Settings;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-import android.view.ContextThemeWrapper;
 import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import java.util.List;
 
 /**
  * Displays a list of contacts. Usually is embedded into the ContactsActivity.
@@ -83,16 +65,13 @@
     private static final int SUBACTIVITY_DISPLAY_GROUP = 3;
     private static final int SUBACTIVITY_SEARCH = 4;
 
-    private final String[] sLookupProjection = new String[] {
-            Contacts.LOOKUP_KEY
-    };
-
     private ContactsIntentResolver mIntentResolver;
     protected ContactEntryListFragment<?> mListFragment;
 
     protected PhoneNumberInteraction mPhoneNumberCallInteraction;
     protected PhoneNumberInteraction mSendTextMessageInteraction;
     private ContactDeletionInteraction mContactDeletionInteraction;
+    private ImportExportInteraction mImportExportInteraction;
 
     private int mActionCode;
 
@@ -102,6 +81,7 @@
     private SearchEditText mSearchEditText;
 
 
+
     public ContactsListActivity() {
         mIntentResolver = new ContactsIntentResolver(this);
     }
@@ -453,7 +433,7 @@
                 return true;
             }
             case R.id.menu_import_export: {
-                displayImportExportDialog();
+                getImportExportInteraction().startInteraction();
                 return true;
             }
             case R.id.menu_accounts: {
@@ -500,19 +480,11 @@
             return dialog;
         }
 
-        switch (id) {
-            case R.string.import_from_sim:
-            case R.string.import_from_sdcard: {
-                return AccountSelectionUtil.getSelectAccountDialog(this, id);
-            }
-            case R.id.dialog_sdcard_not_found: {
-                return new AlertDialog.Builder(this)
-                .setTitle(R.string.no_sdcard_title)
-                .setIcon(android.R.drawable.ic_dialog_alert)
-                .setMessage(R.string.no_sdcard_message)
-                .setPositiveButton(android.R.string.ok, null).create();
-            }
+        dialog = getImportExportInteraction().onCreateDialog(id, bundle);
+        if (dialog != null) {
+            return dialog;
         }
+
         return super.onCreateDialog(id, bundle);
     }
 
@@ -532,128 +504,6 @@
 
         super.onPrepareDialog(id, dialog, bundle);
     }
-    /**
-     * Create a {@link Dialog} that allows the user to pick from a bulk import
-     * or bulk export task across all contacts.
-     */
-    private void displayImportExportDialog() {
-        // Wrap our context to inflate list items using correct theme
-        final Context dialogContext = new ContextThemeWrapper(this, android.R.style.Theme_Light);
-        final Resources res = dialogContext.getResources();
-        final LayoutInflater dialogInflater = (LayoutInflater)dialogContext
-                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
-        // Adapter that shows a list of string resources
-        final ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(this,
-                android.R.layout.simple_list_item_1) {
-            @Override
-            public View getView(int position, View convertView, ViewGroup parent) {
-                if (convertView == null) {
-                    convertView = dialogInflater.inflate(android.R.layout.simple_list_item_1,
-                            parent, false);
-                }
-
-                final int resId = this.getItem(position);
-                ((TextView)convertView).setText(resId);
-                return convertView;
-            }
-        };
-
-        if (TelephonyManager.getDefault().hasIccCard()) {
-            adapter.add(R.string.import_from_sim);
-        }
-        if (res.getBoolean(R.bool.config_allow_import_from_sdcard)) {
-            adapter.add(R.string.import_from_sdcard);
-        }
-        if (res.getBoolean(R.bool.config_allow_export_to_sdcard)) {
-            adapter.add(R.string.export_to_sdcard);
-        }
-        if (res.getBoolean(R.bool.config_allow_share_visible_contacts)) {
-            adapter.add(R.string.share_visible_contacts);
-        }
-
-        final DialogInterface.OnClickListener clickListener =
-                new DialogInterface.OnClickListener() {
-            public void onClick(DialogInterface dialog, int which) {
-                dialog.dismiss();
-
-                final int resId = adapter.getItem(which);
-                switch (resId) {
-                    case R.string.import_from_sim:
-                    case R.string.import_from_sdcard: {
-                        handleImportRequest(resId);
-                        break;
-                    }
-                    case R.string.export_to_sdcard: {
-                        Context context = ContactsListActivity.this;
-                        Intent exportIntent = new Intent(context, ExportVCardActivity.class);
-                        context.startActivity(exportIntent);
-                        break;
-                    }
-                    case R.string.share_visible_contacts: {
-                        doShareVisibleContacts();
-                        break;
-                    }
-                    default: {
-                        Log.e(TAG, "Unexpected resource: " +
-                                getResources().getResourceEntryName(resId));
-                    }
-                }
-            }
-        };
-
-        new AlertDialog.Builder(this)
-            .setTitle(R.string.dialog_import_export)
-            .setNegativeButton(android.R.string.cancel, null)
-            .setSingleChoiceItems(adapter, -1, clickListener)
-            .show();
-    }
-
-    private void doShareVisibleContacts() {
-        final Cursor cursor = getContentResolver().query(Contacts.CONTENT_URI,
-                sLookupProjection, Contacts.IN_VISIBLE_GROUP + "!=0", null, null);
-        try {
-            if (!cursor.moveToFirst()) {
-                Toast.makeText(this, R.string.share_error, Toast.LENGTH_SHORT).show();
-                return;
-            }
-
-            StringBuilder uriListBuilder = new StringBuilder();
-            int index = 0;
-            for (;!cursor.isAfterLast(); cursor.moveToNext()) {
-                if (index != 0)
-                    uriListBuilder.append(':');
-                uriListBuilder.append(cursor.getString(0));
-                index++;
-            }
-            Uri uri = Uri.withAppendedPath(
-                    Contacts.CONTENT_MULTI_VCARD_URI,
-                    Uri.encode(uriListBuilder.toString()));
-
-            final Intent intent = new Intent(Intent.ACTION_SEND);
-            intent.setType(Contacts.CONTENT_VCARD_TYPE);
-            intent.putExtra(Intent.EXTRA_STREAM, uri);
-            startActivity(intent);
-        } finally {
-            cursor.close();
-        }
-    }
-
-    private void handleImportRequest(int resId) {
-        // There's three possibilities:
-        // - more than one accounts -> ask the user
-        // - just one account -> use the account without asking the user
-        // - no account -> use phone-local storage without asking the user
-        final Sources sources = Sources.getInstance(this);
-        final List<Account> accountList = sources.getAccounts(true);
-        final int size = accountList.size();
-        if (size > 1) {
-            showDialog(resId);
-            return;
-        }
-
-        AccountSelectionUtil.doImport(this, resId, (size == 1 ? accountList.get(0) : null));
-    }
 
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
@@ -777,4 +627,11 @@
         }
         return mContactDeletionInteraction;
     }
+
+    private ImportExportInteraction getImportExportInteraction() {
+        if (mImportExportInteraction == null) {
+            mImportExportInteraction = new ImportExportInteraction(this);
+        }
+        return mImportExportInteraction;
+    }
 }
diff --git a/src/com/android/contacts/interactions/ImportExportInteraction.java b/src/com/android/contacts/interactions/ImportExportInteraction.java
new file mode 100644
index 0000000..33df22f
--- /dev/null
+++ b/src/com/android/contacts/interactions/ImportExportInteraction.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2010 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.interactions;
+
+import com.android.contacts.R;
+import com.android.contacts.model.Sources;
+import com.android.contacts.util.AccountSelectionUtil;
+import com.android.contacts.vcard.ExportVCardActivity;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract.Contacts;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.List;
+
+/**
+ * An interaction invoked to import/export contacts.
+ */
+public class ImportExportInteraction {
+
+    private static final String TAG = "ImportExportInteraction";
+
+    private final String[] LOOKUP_PROJECTION = new String[] {
+            Contacts.LOOKUP_KEY
+    };
+
+    private final Context mContext;
+
+    public ImportExportInteraction(Context context) {
+        this.mContext = context;
+    }
+
+    /**
+     * Creates a {@link Dialog} that allows the user to choose between a bulk import
+     * and bulk export task across all contacts.
+     */
+    public void startInteraction() {
+        showDialog(R.id.dialog_import_export_options, null);
+    }
+
+    public Dialog onCreateDialog(int id, Bundle bundle) {
+        switch (id) {
+            case R.id.dialog_import_export_options: {
+                return createOptionsDialog();
+            }
+            case R.string.import_from_sim:
+            case R.string.import_from_sdcard: {
+                return AccountSelectionUtil.getSelectAccountDialog(mContext, id);
+            }
+            case R.id.dialog_sdcard_not_found: {
+                return new AlertDialog.Builder(mContext)
+                        .setTitle(R.string.no_sdcard_title)
+                        .setIcon(android.R.drawable.ic_dialog_alert)
+                        .setMessage(R.string.no_sdcard_message)
+                        .setPositiveButton(android.R.string.ok, null).create();
+            }
+        }
+
+        return null;
+    }
+
+    private Dialog createOptionsDialog() {
+        // Wrap our context to inflate list items using the correct theme
+        final Context dialogContext =
+                new ContextThemeWrapper(mContext, android.R.style.Theme_Light);
+        final Resources res = dialogContext.getResources();
+        final LayoutInflater dialogInflater = (LayoutInflater)dialogContext
+                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+        // Adapter that shows a list of string resources
+        final ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(mContext,
+                android.R.layout.simple_list_item_1) {
+            @Override
+            public View getView(int position, View convertView, ViewGroup parent) {
+                if (convertView == null) {
+                    convertView = dialogInflater.inflate(android.R.layout.simple_list_item_1,
+                            parent, false);
+                }
+
+                final int resId = this.getItem(position);
+                ((TextView)convertView).setText(resId);
+                return convertView;
+            }
+        };
+
+        if (TelephonyManager.getDefault().hasIccCard()) {
+            adapter.add(R.string.import_from_sim);
+        }
+        if (res.getBoolean(R.bool.config_allow_import_from_sdcard)) {
+            adapter.add(R.string.import_from_sdcard);
+        }
+        if (res.getBoolean(R.bool.config_allow_export_to_sdcard)) {
+            adapter.add(R.string.export_to_sdcard);
+        }
+        if (res.getBoolean(R.bool.config_allow_share_visible_contacts)) {
+            adapter.add(R.string.share_visible_contacts);
+        }
+
+        final DialogInterface.OnClickListener clickListener =
+                new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int which) {
+                dialog.dismiss();
+
+                final int resId = adapter.getItem(which);
+                switch (resId) {
+                    case R.string.import_from_sim:
+                    case R.string.import_from_sdcard: {
+                        handleImportRequest(resId);
+                        break;
+                    }
+                    case R.string.export_to_sdcard: {
+                        Intent exportIntent = new Intent(mContext, ExportVCardActivity.class);
+                        mContext.startActivity(exportIntent);
+                        break;
+                    }
+                    case R.string.share_visible_contacts: {
+                        doShareVisibleContacts();
+                        break;
+                    }
+                    default: {
+                        Log.e(TAG, "Unexpected resource: " +
+                                mContext.getResources().getResourceEntryName(resId));
+                    }
+                }
+            }
+        };
+
+        return new AlertDialog.Builder(mContext)
+                .setTitle(R.string.dialog_import_export)
+                .setNegativeButton(android.R.string.cancel, null)
+                .setSingleChoiceItems(adapter, -1, clickListener)
+                .create();
+    }
+
+    private void doShareVisibleContacts() {
+
+        // TODO move the query into a loader
+        final Cursor cursor = mContext.getContentResolver().query(Contacts.CONTENT_URI,
+                LOOKUP_PROJECTION, Contacts.IN_VISIBLE_GROUP + "!=0", null, null);
+        try {
+            if (!cursor.moveToFirst()) {
+                Toast.makeText(mContext, R.string.share_error, Toast.LENGTH_SHORT).show();
+                return;
+            }
+
+            StringBuilder uriListBuilder = new StringBuilder();
+            int index = 0;
+            while (cursor.moveToNext()) {
+                if (index != 0)
+                    uriListBuilder.append(':');
+                uriListBuilder.append(cursor.getString(0));
+                index++;
+            }
+            Uri uri = Uri.withAppendedPath(
+                    Contacts.CONTENT_MULTI_VCARD_URI,
+                    Uri.encode(uriListBuilder.toString()));
+
+            final Intent intent = new Intent(Intent.ACTION_SEND);
+            intent.setType(Contacts.CONTENT_VCARD_TYPE);
+            intent.putExtra(Intent.EXTRA_STREAM, uri);
+            mContext.startActivity(intent);
+        } finally {
+            cursor.close();
+        }
+    }
+
+    private void handleImportRequest(int resId) {
+        // There are three possibilities:
+        // - more than one accounts -> ask the user
+        // - just one account -> use the account without asking the user
+        // - no account -> use phone-local storage without asking the user
+        final Sources sources = Sources.getInstance(mContext);
+        final List<Account> accountList = sources.getAccounts(true);
+        final int size = accountList.size();
+        if (size > 1) {
+            showDialog(resId, null);
+            return;
+        }
+
+        AccountSelectionUtil.doImport(mContext, resId, (size == 1 ? accountList.get(0) : null));
+    }
+
+    /* Visible for testing */
+    void showDialog(int dialogId, Bundle bundle) {
+        ((Activity)mContext).showDialog(dialogId, bundle);
+    }
+}