Adding "make personal copy" feature
The is the first in a series of CLs.
For now we create the copy, but we don't
actually select it in the UI.
Change-Id: Ie2719bf4e91915992f0e785b7a9827b3c934a6a2
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 105b502..982e8db 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1304,4 +1304,7 @@
<!-- The description of the directory where the contact was found [CHAR LIMIT=100]-->
<string name="contact_directory_account_description">from <xliff:g id="type" example="Corporate Directory">%1$s</xliff:g> (<xliff:g id="name" example="me@acme.com">%2$s</xliff:g>)</string>
+
+ <!-- Toast shown when creating a personal copy of a contact [CHAR LIMIT=100] -->
+ <string name="toast_making_personal_copy">Creating a personal copy</string>
</resources>
diff --git a/src/com/android/contacts/activities/ContactBrowserActivity.java b/src/com/android/contacts/activities/ContactBrowserActivity.java
index db353e8..6a68547 100644
--- a/src/com/android/contacts/activities/ContactBrowserActivity.java
+++ b/src/com/android/contacts/activities/ContactBrowserActivity.java
@@ -30,11 +30,13 @@
import com.android.contacts.list.StrequentContactListFragment;
import com.android.contacts.ui.ContactsPreferencesActivity;
import com.android.contacts.util.DialogManager;
+import com.android.contacts.views.ContactSaveService;
import com.android.contacts.views.detail.ContactDetailFragment;
import com.android.contacts.views.detail.ContactNoneFragment;
import com.android.contacts.views.editor.ContactEditorFragment;
import com.android.contacts.widget.ContextMenuAdapter;
+import android.accounts.Account;
import android.app.ActionBar;
import android.app.Activity;
import android.app.Dialog;
@@ -57,6 +59,8 @@
import android.view.Window;
import android.widget.Toast;
+import java.util.ArrayList;
+
/**
* Displays a list to browse contacts. For xlarge screens, this also displays a detail-pane on
* the right
@@ -169,6 +173,26 @@
}
}
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ if (Intent.ACTION_VIEW.equals(intent.getAction())) {
+ Uri uri = intent.getData();
+ if (uri == null) {
+ return;
+ }
+
+ if (mHasActionBar) {
+ if (mActionBarAdapter.getMode() != ContactBrowserMode.MODE_CONTACTS) {
+ mActionBarAdapter.clearSavedState(ContactBrowserMode.MODE_CONTACTS);
+ mActionBarAdapter.setMode(ContactBrowserMode.MODE_CONTACTS);
+ }
+ }
+ mListFragment.setSelectedContactUri(uri);
+ setupContactDetailFragment(uri);
+ }
+ }
+
private void configureListFragment() {
int mode = -1;
if (mHasActionBar) {
@@ -503,6 +527,15 @@
public void onDeleteRequested(Uri contactLookupUri) {
getContactDeletionInteraction().deleteContact(contactLookupUri);
}
+
+ @Override
+ public void onCreateRawContactRequested(ArrayList<ContentValues> values, Account account) {
+ Toast.makeText(ContactBrowserActivity.this, R.string.toast_making_personal_copy,
+ Toast.LENGTH_LONG).show();
+ Intent serviceIntent = ContactSaveService.createNewRawContactIntent(
+ ContactBrowserActivity.this, values, account);
+ startService(serviceIntent);
+ }
}
private class EditorFragmentListener implements ContactEditorFragment.Listener {
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index b07ee36..b26cbd0 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -19,17 +19,23 @@
import com.android.contacts.ContactsSearchManager;
import com.android.contacts.R;
import com.android.contacts.interactions.ContactDeletionInteraction;
+import com.android.contacts.views.ContactSaveService;
import com.android.contacts.views.detail.ContactDetailFragment;
+import android.accounts.Account;
import android.app.Activity;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
+import android.content.ContentValues;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MenuItem;
+import android.widget.Toast;
+
+import java.util.ArrayList;
public class ContactDetailActivity extends Activity {
private static final String TAG = "ContactDetailActivity";
@@ -127,5 +133,16 @@
public void onDeleteRequested(Uri lookupUri) {
getContactDeletionInteraction().deleteContact(lookupUri);
}
+
+ @Override
+ public void onCreateRawContactRequested(
+ ArrayList<ContentValues> values, Account account) {
+ Toast.makeText(ContactDetailActivity.this, R.string.toast_making_personal_copy,
+ Toast.LENGTH_LONG).show();
+ Intent serviceIntent = ContactSaveService.createNewRawContactIntent(
+ ContactDetailActivity.this, values, account);
+ startService(serviceIntent);
+
+ }
};
}
diff --git a/src/com/android/contacts/list/ContactBrowseListFragment.java b/src/com/android/contacts/list/ContactBrowseListFragment.java
index c09dc47..fe82129 100644
--- a/src/com/android/contacts/list/ContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/ContactBrowseListFragment.java
@@ -138,12 +138,14 @@
parseSelectedContactUri();
- // Configure the adapter to show the selection based on the lookup key extracted
- // from the URI
- configureAdapter();
+ if (isAdded()) {
+ // Configure the adapter to show the selection based on the lookup key extracted
+ // from the URI
+ configureAdapter();
- // Also, launch a loader to pick up a new lookup key in case it has changed
- startLoadingContactLookupKey();
+ // Also, launch a loader to pick up a new lookup key in case it has changed
+ startLoadingContactLookupKey();
+ }
}
}
diff --git a/src/com/android/contacts/views/ContactLoader.java b/src/com/android/contacts/views/ContactLoader.java
index 7a221a1..19a28b5 100644
--- a/src/com/android/contacts/views/ContactLoader.java
+++ b/src/com/android/contacts/views/ContactLoader.java
@@ -24,6 +24,7 @@
import android.content.ContentValues;
import android.content.Context;
import android.content.Entity;
+import android.content.Entity.NamedContentValues;
import android.content.Loader;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -44,6 +45,7 @@
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
/**
* Loads a single Contact and all it constituent RawContacts.
@@ -97,6 +99,7 @@
private String mDirectoryDisplayName;
private String mDirectoryType;
+ private String mDirectoryAccountType;
private String mDirectoryAccountName;
private int mDirectoryExportSupport;
@@ -156,9 +159,10 @@
* @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
*/
public void setDirectoryMetaData(String displayName, String directoryType,
- String accountName, int exportSupport) {
+ String accountType, String accountName, int exportSupport) {
mDirectoryDisplayName = displayName;
mDirectoryType = directoryType;
+ mDirectoryAccountType = accountType;
mDirectoryAccountName = accountName;
mDirectoryExportSupport = exportSupport;
}
@@ -236,10 +240,34 @@
return mDirectoryType;
}
+ public String getDirectoryAccountType() {
+ return mDirectoryAccountType;
+ }
+
public String getDirectoryAccountName() {
return mDirectoryAccountName;
}
+ public ArrayList<ContentValues> getContentValues() {
+ if (mEntities.size() != 1) {
+ throw new IllegalStateException(
+ "Cannot extract content values from an aggregated contact");
+ }
+
+ Entity entity = mEntities.get(0);
+ ArrayList<ContentValues> result = new ArrayList<ContentValues>();
+ ArrayList<NamedContentValues> subValues = entity.getSubValues();
+ if (subValues != null) {
+ int size = subValues.size();
+ for (int i = 0; i < size; i++) {
+ NamedContentValues pair = subValues.get(i);
+ if (Data.CONTENT_URI.equals(pair.uri)) {
+ result.add(pair.values);
+ }
+ }
+ }
+ return result;
+ }
}
private static class ContactQuery {
@@ -377,6 +405,7 @@
Directory.DISPLAY_NAME,
Directory.PACKAGE_NAME,
Directory.TYPE_RESOURCE_ID,
+ Directory.ACCOUNT_TYPE,
Directory.ACCOUNT_NAME,
Directory.EXPORT_SUPPORT,
};
@@ -384,8 +413,9 @@
public final static int DISPLAY_NAME = 0;
public final static int PACKAGE_NAME = 1;
public final static int TYPE_RESOURCE_ID = 2;
- public final static int ACCOUNT_NAME = 3;
- public final static int EXPORT_SUPPORT = 4;
+ public final static int ACCOUNT_TYPE = 3;
+ public final static int ACCOUNT_NAME = 4;
+ public final static int EXPORT_SUPPORT = 5;
}
private final class LoadContactTask extends AsyncTask<Void, Void, Result> {
@@ -628,6 +658,7 @@
final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
+ final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
String directoryType = null;
@@ -643,7 +674,7 @@
}
result.setDirectoryMetaData(
- displayName, directoryType, accountName, exportSupport);
+ displayName, directoryType, accountType, accountName, exportSupport);
}
} finally {
cursor.close();
diff --git a/src/com/android/contacts/views/ContactSaveService.java b/src/com/android/contacts/views/ContactSaveService.java
index 936b8a4..91d2413 100644
--- a/src/com/android/contacts/views/ContactSaveService.java
+++ b/src/com/android/contacts/views/ContactSaveService.java
@@ -16,22 +16,62 @@
package com.android.contacts.views;
+import com.android.contacts.activities.ContactBrowserActivity;
+import com.google.android.collect.Sets;
+
+import android.accounts.Account;
+import android.app.Activity;
import android.app.IntentService;
import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.Intent;
import android.content.OperationApplicationException;
+import android.net.Uri;
import android.os.Parcelable;
import android.os.RemoteException;
import android.provider.ContactsContract;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
import android.util.Log;
import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
public class ContactSaveService extends IntentService {
private static final String TAG = "ContactSaveService";
+ public static final String ACTION_NEW_RAW_CONTACT = "newRawContact";
+
+ public static final String EXTRA_ACCOUNT_NAME = "accountName";
+ public static final String EXTRA_ACCOUNT_TYPE = "accountType";
+ public static final String EXTRA_CONTENT_VALUES = "contentValues";
+ public static final String EXTRA_CALLBACK_INTENT = "callbackIntent";
+
public static final String EXTRA_OPERATIONS = "Operations";
+ private static final HashSet<String> ALLOWED_DATA_COLUMNS = Sets.newHashSet(
+ Data.MIMETYPE,
+ Data.IS_PRIMARY,
+ Data.DATA1,
+ Data.DATA2,
+ Data.DATA3,
+ Data.DATA4,
+ Data.DATA5,
+ Data.DATA6,
+ Data.DATA7,
+ Data.DATA8,
+ Data.DATA9,
+ Data.DATA10,
+ Data.DATA11,
+ Data.DATA12,
+ Data.DATA13,
+ Data.DATA14,
+ Data.DATA15
+ );
+
public ContactSaveService() {
super(TAG);
setIntentRedelivery(true);
@@ -39,6 +79,52 @@
@Override
protected void onHandleIntent(Intent intent) {
+ String action = intent.getAction();
+ if (ACTION_NEW_RAW_CONTACT.equals(action)) {
+ createRawContact(intent);
+ } else {
+ performContentProviderOperations(intent);
+ }
+ }
+
+ private void createRawContact(Intent intent) {
+ String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
+ String accountType = intent.getStringExtra(EXTRA_ACCOUNT_TYPE);
+ List<ContentValues> valueList = intent.getParcelableArrayListExtra(EXTRA_CONTENT_VALUES);
+ Intent callbackIntent = intent.getParcelableExtra(EXTRA_CALLBACK_INTENT);
+
+ ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
+ operations.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
+ .withValue(RawContacts.ACCOUNT_NAME, accountName)
+ .withValue(RawContacts.ACCOUNT_TYPE, accountType)
+ .build());
+
+ int size = valueList.size();
+ for (int i = 0; i < size; i++) {
+ ContentValues values = valueList.get(i);
+ values.keySet().retainAll(ALLOWED_DATA_COLUMNS);
+ operations.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)
+ .withValueBackReference(Data.RAW_CONTACT_ID, 0)
+ .withValues(values)
+ .build());
+ }
+
+ ContentResolver resolver = getContentResolver();
+ ContentProviderResult[] results;
+ try {
+ results = resolver.applyBatch(ContactsContract.AUTHORITY, operations);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to store new contact", e);
+ }
+
+ Uri result = ContactsContract.Directory.CONTENT_URI;
+ Uri rawContactUri = results[0].uri;
+ callbackIntent.setData(RawContacts.getContactLookupUri(resolver, rawContactUri));
+
+ startActivity(callbackIntent);
+ }
+
+ private void performContentProviderOperations(Intent intent) {
final Parcelable[] operationsArray = intent.getParcelableArrayExtra(EXTRA_OPERATIONS);
// We have to cast each item individually here
@@ -56,4 +142,31 @@
Log.e(TAG, "Error saving", e);
}
}
+
+ /**
+ * Creates an intent that can be sent to this service to create a new raw contact
+ * using data presented as a set of ContentValues.
+ */
+ public static Intent createNewRawContactIntent(
+ Activity activity, ArrayList<ContentValues> values, Account account) {
+ Intent serviceIntent = new Intent(
+ activity, ContactSaveService.class);
+ serviceIntent.setAction(ContactSaveService.ACTION_NEW_RAW_CONTACT);
+ if (account != null) {
+ serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_NAME, account.name);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_ACCOUNT_TYPE, account.type);
+ }
+ serviceIntent.putParcelableArrayListExtra(
+ ContactSaveService.EXTRA_CONTENT_VALUES, values);
+
+ // Callback intent will be invoked by the service once the new contact is
+ // created. The service will put the URI of the new contact as "data" on
+ // the callback intent.
+ Intent callbackIntent = new Intent(activity, activity.getClass());
+ callbackIntent.setAction(Intent.ACTION_VIEW);
+ callbackIntent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ serviceIntent.putExtra(ContactSaveService.EXTRA_CALLBACK_INTENT, callbackIntent);
+ return serviceIntent;
+ }
}
diff --git a/src/com/android/contacts/views/detail/ContactDetailFragment.java b/src/com/android/contacts/views/detail/ContactDetailFragment.java
index 436e9b0..c734f84 100644
--- a/src/com/android/contacts/views/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/views/detail/ContactDetailFragment.java
@@ -17,22 +17,24 @@
package com.android.contacts.views.detail;
import com.android.contacts.Collapser;
+import com.android.contacts.Collapser.Collapsible;
import com.android.contacts.ContactOptionsActivity;
import com.android.contacts.ContactPresenceIconUtil;
import com.android.contacts.ContactsUtils;
import com.android.contacts.ContactsUtils.ImActions;
import com.android.contacts.R;
import com.android.contacts.TypePrecedence;
-import com.android.contacts.Collapser.Collapsible;
import com.android.contacts.model.ContactsSource;
-import com.android.contacts.model.Sources;
import com.android.contacts.model.ContactsSource.DataKind;
+import com.android.contacts.model.Sources;
import com.android.contacts.util.Constants;
import com.android.contacts.util.DataStatus;
import com.android.contacts.util.PhoneCapabilityTester;
import com.android.contacts.views.ContactLoader;
+import com.android.contacts.views.editor.SelectAccountDialogFragment;
import com.android.internal.telephony.ITelephony;
+import android.accounts.Account;
import android.app.Activity;
import android.app.Fragment;
import android.app.LoaderManager;
@@ -42,9 +44,9 @@
import android.content.ContentValues;
import android.content.Context;
import android.content.Entity;
+import android.content.Entity.NamedContentValues;
import android.content.Intent;
import android.content.Loader;
-import android.content.Entity.NamedContentValues;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.ParseException;
@@ -54,12 +56,6 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.ContactsContract.CommonDataKinds;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.Directory;
-import android.provider.ContactsContract.DisplayNameSources;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.StatusUpdates;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
@@ -70,32 +66,38 @@
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.DisplayNameSources;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.StatusUpdates;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
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.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
+import android.view.ViewGroup;
import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
-import android.widget.AdapterView.OnItemClickListener;
import java.util.ArrayList;
-public class ContactDetailFragment extends Fragment
- implements OnCreateContextMenuListener, OnItemClickListener {
+public class ContactDetailFragment extends Fragment implements OnCreateContextMenuListener,
+ OnItemClickListener, SelectAccountDialogFragment.Listener {
private static final String TAG = "ContactDetailFragment";
private static final int MENU_ITEM_MAKE_DEFAULT = 3;
@@ -883,13 +885,62 @@
return true;
}
case R.id.menu_copy: {
- Toast.makeText(mContext, "Not implemented yet", Toast.LENGTH_SHORT).show();
+ makePersonalCopy();
return true;
}
}
return false;
}
+ private void makePersonalCopy() {
+ if (mListener == null) {
+ return;
+ }
+
+ int exportSupport = mContactData.getDirectoryExportSupport();
+ switch (exportSupport) {
+ case Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY: {
+ createCopy(new Account(mContactData.getDirectoryAccountName(),
+ mContactData.getDirectoryAccountType()));
+ break;
+ }
+ case Directory.EXPORT_SUPPORT_ANY_ACCOUNT: {
+ final ArrayList<Account> accounts = Sources.getInstance(mContext).getAccounts(true);
+ if (accounts.isEmpty()) {
+ createCopy(null);
+ return; // Don't show a dialog.
+ }
+
+ // In the common case of a single writable account, auto-select
+ // it without showing a dialog.
+ if (accounts.size() == 1) {
+ createCopy(accounts.get(0));
+ return; // Don't show a dialog.
+ }
+
+ final SelectAccountDialogFragment dialog =
+ new SelectAccountDialogFragment(getId(), true);
+ dialog.show(getFragmentManager(), SelectAccountDialogFragment.TAG);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onAccountSelectorCancelled() {
+ }
+
+ @Override
+ public void onAccountChosen(Account account, boolean isNewContact) {
+ createCopy(account);
+ }
+
+ private void createCopy(Account account) {
+ if (mListener != null) {
+ mListener.onCreateRawContactRequested(mContactData.getContentValues(), account);
+ }
+ }
+
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
AdapterView.AdapterContextMenuInfo info;
@@ -1077,5 +1128,13 @@
* User decided to delete the contact
*/
public void onDeleteRequested(Uri lookupUri);
+
+ /**
+ * User requested creation of a new contact with the specified values.
+ *
+ * @param values ContentValues containing data rows for the new contact.
+ * @param account Account where the new contact should be created
+ */
+ public void onCreateRawContactRequested(ArrayList<ContentValues> values, Account account);
}
}
diff --git a/src/com/android/contacts/views/editor/SelectAccountDialogFragment.java b/src/com/android/contacts/views/editor/SelectAccountDialogFragment.java
index 9ea1955..3740fe3 100644
--- a/src/com/android/contacts/views/editor/SelectAccountDialogFragment.java
+++ b/src/com/android/contacts/views/editor/SelectAccountDialogFragment.java
@@ -43,7 +43,7 @@
* Does not perform any action by itself.
*/
public class SelectAccountDialogFragment extends TargetedDialogFragment {
- public static final String TAG = "PickPhotoDialogFragment";
+ public static final String TAG = "SelectAccountDialogFragment";
private static final String IS_NEW_CONTACT = "IS_NEW_CONTACT";
private boolean mIsNewContact;
@@ -70,6 +70,7 @@
outState.putBoolean(IS_NEW_CONTACT, mIsNewContact);
}
+ @Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Wrap our context to inflate list items using correct theme
final Context dialogContext = new ContextThemeWrapper(getActivity(),