Merge "Implemented all missing functionality from Editor-Fragment (minus Statehandling)"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0eb5809..0994728 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -399,7 +399,7 @@
<!-- Edit or insert details for a contact -->
<activity
- android:name=".ui.EditContactActivity"
+ android:name=".activities.ContactEditActivity"
android:windowSoftInputMode="stateHidden|adjustResize">
<intent-filter android:label="@string/editContactDescription">
diff --git a/res/values/ids.xml b/res/values/ids.xml
index 83a0cf3..3da8741 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -34,8 +34,11 @@
<item type="id" name="edit_dialog_pick_photo"/>
<item type="id" name="edit_dialog_split"/>
<item type="id" name="edit_dialog_select_account"/>
- <item type="id" name="edit_dialog_view_dialogs_id1"/>
- <item type="id" name="edit_dialog_view_dialogs_id2"/>
+
+ <!-- Request Codes (startActivityForResult) -->
+ <item type="id" name="edit_request_code_join"/>
+ <item type="id" name="edit_request_code_camera_with_data"/>
+ <item type="id" name="edit_request_code_photo_picked_with_data"/>
<!-- For ImportVCardActivity -->
<item type="id" name="dialog_searching_vcard"/>
diff --git a/src/com/android/contacts/activities/ContactEditActivity.java b/src/com/android/contacts/activities/ContactEditActivity.java
index 8834daf..b7e458c 100644
--- a/src/com/android/contacts/activities/ContactEditActivity.java
+++ b/src/com/android/contacts/activities/ContactEditActivity.java
@@ -16,6 +16,7 @@
package com.android.contacts.activities;
+import com.android.contacts.ContactsSearchManager;
import com.android.contacts.R;
import com.android.contacts.util.DialogManager;
import com.android.contacts.views.edit.ContactEditFragment;
@@ -26,6 +27,8 @@
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
public class ContactEditActivity extends Activity implements
DialogManager.DialogShowingViewActivity {
@@ -62,10 +65,22 @@
finish();
}
+ public void closeAfterDelete() {
+ finish();
+ }
+
public void closeBecauseContactNotFound() {
finish();
}
+ public void closeAfterSplit() {
+ finish();
+ }
+
+ public void closeBecauseAccountSelectorAborted() {
+ finish();
+ }
+
public void setTitleTo(int resourceId) {
setTitle(resourceId);
}
@@ -97,4 +112,44 @@
Log.w(TAG, "Unknown dialog requested, id: " + id + ", args: " + args);
return null;
}
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ // TODO: This is too hardwired.
+ mFragment.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // TODO: This is too hardwired.
+ if (mFragment.onCreateOptionsMenu(menu, getMenuInflater())) return true;
+
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ // TODO: This is too hardwired.
+ if (mFragment.onPrepareOptionsMenu(menu)) return true;
+
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // TODO: This is too hardwired.
+ if (mFragment.onOptionsItemSelected(item)) return true;
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
+ boolean globalSearch) {
+ if (globalSearch) {
+ super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
+ } else {
+ ContactsSearchManager.startSearch(this, initialQuery);
+ }
+ }
}
diff --git a/src/com/android/contacts/views/detail/ContactDetailFragment.java b/src/com/android/contacts/views/detail/ContactDetailFragment.java
index 51c115b..5606272 100644
--- a/src/com/android/contacts/views/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/views/detail/ContactDetailFragment.java
@@ -793,7 +793,6 @@
final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI,
rawContactIdToEdit);
mCallbacks.editContact(rawContactUri);
- //mContext.startActivity(new Intent(Intent.ACTION_EDIT, rawContactUri));
return true;
} else {
// There is no rawContact to edit.
diff --git a/src/com/android/contacts/views/edit/ContactEditFragment.java b/src/com/android/contacts/views/edit/ContactEditFragment.java
index 87c2638..95e55cd 100644
--- a/src/com/android/contacts/views/edit/ContactEditFragment.java
+++ b/src/com/android/contacts/views/edit/ContactEditFragment.java
@@ -54,6 +54,8 @@
import android.content.ContentProviderOperation.Builder;
import android.content.DialogInterface.OnDismissListener;
import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -69,6 +71,9 @@
import android.util.Log;
import android.view.ContextThemeWrapper;
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.View.OnClickListener;
@@ -86,21 +91,12 @@
import java.util.Comparator;
import java.util.Date;
-public class ContactEditFragment
- extends LoaderManagingFragment<ContactEditLoader.Result> {
+public class ContactEditFragment extends LoaderManagingFragment<ContactEditLoader.Result> {
+
private static final String TAG = "ContactEditFragment";
private static final int LOADER_DATA = 1;
- /** The launch code when picking a photo and the raw data is returned */
- private static final int PHOTO_PICKED_WITH_DATA = 3021;
-
- /** The launch code when a contact to join with is returned */
- private static final int REQUEST_JOIN_CONTACT = 3022;
-
- /** The launch code when taking a picture */
- private static final int CAMERA_WITH_DATA = 3023;
-
private static final String KEY_EDIT_STATE = "state";
private static final String KEY_RAW_CONTACT_ID_REQUESTING_PHOTO = "photorequester";
private static final String KEY_VIEW_ID_GENERATOR = "viewidgenerator";
@@ -109,29 +105,30 @@
private static final String KEY_QUERY_SELECTION_ARGS = "queryselectionargs";
private static final String KEY_CONTACT_ID_FOR_JOIN = "contactidforjoin";
- private static final String BUNDLE_SELECT_ACCOUNT_LIST = "account_list";
-
public static final int SAVE_MODE_DEFAULT = 0;
public static final int SAVE_MODE_SPLIT = 1;
public static final int SAVE_MODE_JOIN = 2;
private long mRawContactIdRequestingPhoto = -1;
+ private final EntityDeltaComparator mComparator = new EntityDeltaComparator();
+
+ private static final String BUNDLE_SELECT_ACCOUNT_LIST = "account_list";
+
private static final int ICON_SIZE = 96;
private static final File PHOTO_DIR = new File(
Environment.getExternalStorageDirectory() + "/DCIM/Camera");
+ private File mCurrentPhotoFile;
+
private Context mContext;
- private final EntityDeltaComparator mComparator = new EntityDeltaComparator();
private String mAction;
private Uri mUri;
private String mMimeType;
private Bundle mIntentExtras;
private Callbacks mCallbacks;
- private File mCurrentPhotoFile;
-
private String mQuerySelection;
private String[] mQuerySelectionArgs;
@@ -168,13 +165,14 @@
});
view.findViewById(R.id.btn_discard).setOnClickListener(new OnClickListener() {
public void onClick(View v) {
- if (mCallbacks != null) mCallbacks.closeAfterRevert();
+ doRevertAction();
}
});
return view;
}
+ // TODO: Think about splitting this. Doing INSERT via load is kinda weird
public void load(String action, Uri uri, String mimeType, Bundle intentExtras) {
mAction = action;
mUri = uri;
@@ -188,19 +186,7 @@
} else if (Intent.ACTION_INSERT.equals(mAction)) {
if (mCallbacks != null) mCallbacks.setTitleTo(R.string.editContact_title_insert);
- // Load Accounts async so that we can present them
- AsyncTask<Void, Void, ArrayList<Account>> loadAccountsTask =
- new AsyncTask<Void, Void, ArrayList<Account>>() {
- @Override
- protected ArrayList<Account> doInBackground(Void... params) {
- return Sources.getInstance(mContext).getAccounts(true);
- }
- @Override
- protected void onPostExecute(ArrayList<Account> result) {
- selectAccountAndCreateContact(result, true);
- }
- };
- loadAccountsTask.execute();
+ doAddAction();
} else throw new IllegalArgumentException("Unknown Action String " + mAction +
". Only support " + Intent.ACTION_EDIT + " or " + Intent.ACTION_INSERT);
}
@@ -214,35 +200,6 @@
// TODO: Currently savedState is always null (framework issue). Test once this is fixed
super.onCreate(savedState);
- // Handle initial actions only when existing state missing
- final boolean hasIncomingState =
- savedState != null && savedState.containsKey(KEY_EDIT_STATE);
-
- if (!hasIncomingState) {
-// if (Intent.ACTION_EDIT.equals(mAction)) {
-// // Read initial state from database
-// if (mCallbacks != null) mCallbacks.setTitleTo(R.string.editContact_title_edit);
-// startLoading(LOADER_DATA, null);
-// } else if (Intent.ACTION_INSERT.equals(mAction)) {
-// if (mCallbacks != null) mCallbacks.setTitleTo(R.string.editContact_title_insert);
-//
-// // Load Accounts async so that we can present them
-// AsyncTask<Void, Void, ArrayList<Account>> loadAccountsTask =
-// new AsyncTask<Void, Void, ArrayList<Account>>() {
-// @Override
-// protected ArrayList<Account> doInBackground(Void... params) {
-// return Sources.getInstance(mContext).getAccounts(true);
-// }
-// @Override
-// protected void onPostExecute(ArrayList<Account> result) {
-// selectAccountAndCreateContact(result, true);
-// }
-// };
-// loadAccountsTask.execute();
-// } else throw new IllegalArgumentException("Unknown Action String " + mAction +
-// ". Only support " + Intent.ACTION_EDIT + " or " + Intent.ACTION_INSERT);
- }
-
if (savedState == null) {
// If savedState is non-null, onRestoreInstanceState() will restore the generator.
mViewIdGenerator = new ViewIdGenerator();
@@ -375,8 +332,85 @@
mContent.setVisibility(View.VISIBLE);
}
- public void onClick(View v) {
- // TODO forward to controller?
+ public boolean onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.edit, menu);
+
+ return true;
+ }
+
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ menu.findItem(R.id.menu_split).setVisible(mState != null && mState.size() > 1);
+ return true;
+ }
+
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_done:
+ return doSaveAction(SAVE_MODE_DEFAULT);
+ case R.id.menu_discard:
+ return doRevertAction();
+ case R.id.menu_add:
+ return doAddAction();
+ case R.id.menu_delete:
+ return doDeleteAction();
+ case R.id.menu_split:
+ return doSplitContactAction();
+ case R.id.menu_join:
+ return doJoinContactAction();
+ }
+ return false;
+ }
+
+ private boolean doAddAction() {
+ // Load Accounts async so that we can present them
+ AsyncTask<Void, Void, ArrayList<Account>> loadAccountsTask =
+ new AsyncTask<Void, Void, ArrayList<Account>>() {
+ @Override
+ protected ArrayList<Account> doInBackground(Void... params) {
+ return Sources.getInstance(mContext).getAccounts(true);
+ }
+ @Override
+ protected void onPostExecute(ArrayList<Account> result) {
+ selectAccountAndCreateContact(result, true);
+ }
+ };
+ loadAccountsTask.execute();
+
+ return true;
+ }
+
+ /**
+ * Delete the entire contact currently being edited, which usually asks for
+ * user confirmation before continuing.
+ */
+ private boolean doDeleteAction() {
+ if (!hasValidState())
+ return false;
+ int readOnlySourcesCnt = 0;
+ int writableSourcesCnt = 0;
+ // TODO: This shouldn't be called from the UI thread
+ final Sources sources = Sources.getInstance(mContext);
+ for (EntityDelta delta : mState) {
+ final String accountType = delta.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+ final ContactsSource contactsSource = sources.getInflatedSource(accountType,
+ ContactsSource.LEVEL_CONSTRAINTS);
+ if (contactsSource != null && contactsSource.readOnly) {
+ readOnlySourcesCnt += 1;
+ } else {
+ writableSourcesCnt += 1;
+ }
+ }
+
+ if (readOnlySourcesCnt > 0 && writableSourcesCnt > 0) {
+ getActivity().showDialog(R.id.edit_dialog_confirm_readonly_delete);
+ } else if (readOnlySourcesCnt > 0 && writableSourcesCnt == 0) {
+ getActivity().showDialog(R.id.edit_dialog_confirm_readonly_hide);
+ } else if (readOnlySourcesCnt == 0 && writableSourcesCnt > 1) {
+ getActivity().showDialog(R.id.edit_dialog_confirm_multiple_delete);
+ } else {
+ getActivity().showDialog(R.id.edit_dialog_confirm_delete);
+ }
+ return true;
}
/**
@@ -489,8 +523,7 @@
public void onCancel(DialogInterface dialog) {
// If nothing remains, close activity
if (!hasValidState()) {
- // TODO: pass this back to Activity
- // finish();
+ mCallbacks.closeBecauseAccountSelectorAborted();
}
}
};
@@ -509,6 +542,13 @@
return result;
}
+ private boolean doSplitContactAction() {
+ if (!hasValidState()) return false;
+
+ getActivity().showDialog(R.id.edit_dialog_split);
+ return true;
+ }
+
private Dialog createSplitDialog() {
final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setTitle(R.string.splitConfirmation_title);
@@ -526,6 +566,10 @@
return builder.create();
}
+ private boolean doJoinContactAction() {
+ return doSaveAction(SAVE_MODE_JOIN);
+ }
+
/**
* Creates a dialog offering two options: take a photo or pick a photo from the gallery.
*/
@@ -566,8 +610,8 @@
try {
// Launch picker to choose photo for selected contact
final Intent intent = getPhotoPickIntent();
- // TODO: Do this again (Controller?)
- //startActivityForResult(intent, PHOTO_PICKED_WITH_DATA);
+ getActivity().startActivityForResult(intent,
+ R.id.edit_request_code_photo_picked_with_data);
} catch (ActivityNotFoundException e) {
Toast.makeText(mContext, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
}
@@ -615,8 +659,7 @@
mCurrentPhotoFile = new File(PHOTO_DIR, getPhotoFileName());
final Intent intent = getTakePickIntent(mCurrentPhotoFile);
- // TODO: Start camera
- //startActivityForResult(intent, CAMERA_WITH_DATA);
+ getActivity().startActivityForResult(intent, R.id.edit_request_code_camera_with_data);
} catch (ActivityNotFoundException e) {
Toast.makeText(mContext, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
}
@@ -632,6 +675,43 @@
}
/**
+ * Sends a newly acquired photo to Gallery for cropping
+ */
+ protected void doCropPhoto(File f) {
+ try {
+ // Add the image to the media store
+ MediaScannerConnection.scanFile(
+ mContext,
+ new String[] { f.getAbsolutePath() },
+ new String[] { null },
+ null);
+
+ // Launch gallery to crop the photo
+ final Intent intent = getCropImageIntent(Uri.fromFile(f));
+ getActivity().startActivityForResult(intent,
+ R.id.edit_request_code_photo_picked_with_data);
+ } catch (Exception e) {
+ Log.e(TAG, "Cannot crop image", e);
+ Toast.makeText(mContext, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ /**
+ * Constructs an intent for image cropping.
+ */
+ public static Intent getCropImageIntent(Uri photoUri) {
+ Intent intent = new Intent("com.android.camera.action.CROP");
+ intent.setDataAndType(photoUri, "image/*");
+ intent.putExtra("crop", "true");
+ intent.putExtra("aspectX", 1);
+ intent.putExtra("aspectY", 1);
+ intent.putExtra("outputX", ICON_SIZE);
+ intent.putExtra("outputY", ICON_SIZE);
+ intent.putExtra("return-data", true);
+ return intent;
+ }
+
+ /**
* Saves or creates the contact based on the mode, and if successful
* finishes the activity.
*/
@@ -648,6 +728,12 @@
return true;
}
+ private boolean doRevertAction() {
+ if (mCallbacks != null) mCallbacks.closeAfterRevert();
+
+ return true;
+ }
+
private void onSaveCompleted(boolean success, int saveMode, Uri contactLookupUri) {
switch (saveMode) {
case SAVE_MODE_DEFAULT:
@@ -679,23 +765,16 @@
}
if (mCallbacks != null) mCallbacks.closeAfterSaving(resultCode, resultIntent);
break;
-// TODO: Other save modes
+ case SAVE_MODE_SPLIT:
+ if (mCallbacks != null) mCallbacks.closeAfterSplit();
+ break;
-// case SAVE_MODE_SPLIT:
-// if (success) {
-// Intent intent = new Intent();
-// intent.setData(contactLookupUri);
-// setResult(RESULT_CLOSE_VIEW_ACTIVITY, intent);
-// }
-// finish();
-// break;
-//
-// case SAVE_MODE_JOIN:
-// mStatus = STATUS_EDITING;
-// if (success) {
-// showJoinAggregateActivity(contactLookupUri);
-// }
-// break;
+ case SAVE_MODE_JOIN:
+ //mStatus = STATUS_EDITING;
+ if (success) {
+ showJoinAggregateActivity(contactLookupUri);
+ }
+ break;
}
}
@@ -704,15 +783,15 @@
*
* @param contactLookupUri the fresh URI for the currently edited contact (after saving it)
*/
- public void showJoinAggregateActivity(Uri contactLookupUri) {
+ private void showJoinAggregateActivity(Uri contactLookupUri) {
if (contactLookupUri == null) {
return;
}
mContactIdForJoin = ContentUris.parseId(contactLookupUri);
- Intent intent = new Intent(JoinContactActivity.JOIN_CONTACT);
+ final Intent intent = new Intent(JoinContactActivity.JOIN_CONTACT);
intent.putExtra(JoinContactActivity.EXTRA_TARGET_CONTACT_ID, mContactIdForJoin);
- getActivity().startActivityForResult(intent, REQUEST_JOIN_CONTACT);
+ getActivity().startActivityForResult(intent, R.id.edit_request_code_join);
}
private interface JoinContactQuery {
@@ -820,11 +899,26 @@
void closeBecauseContactNotFound();
/**
+ * Contact was split, so we can close now
+ */
+ void closeAfterSplit();
+
+ /**
+ * User was presented with an account selection and couldn't decide.
+ */
+ void closeBecauseAccountSelectorAborted();
+
+ /**
* User has tapped Revert, close the fragment now.
*/
void closeAfterRevert();
/**
+ * User has removed the contact, close the fragment now.
+ */
+ void closeAfterDelete();
+
+ /**
* Set the Title (e.g. of the Activity)
*/
void setTitleTo(int resourceId);
@@ -1010,15 +1104,15 @@
private class DeleteClickListener implements DialogInterface.OnClickListener {
public void onClick(DialogInterface dialog, int which) {
- // TODO: Delete
-// Sources sources = Sources.getInstance(mContext);
-// // Mark all raw contacts for deletion
-// for (EntityDelta delta : mState) {
-// delta.markDeleted();
-// }
-// // Save the deletes
-// doSaveAction(SAVE_MODE_DEFAULT);
-// finish();
+ // TODO: Don't do this from the UI thread
+ final Sources sources = Sources.getInstance(mContext);
+ // Mark all raw contacts for deletion
+ for (EntityDelta delta : mState) {
+ delta.markDeleted();
+ }
+ // Save the deletes
+ doSaveAction(SAVE_MODE_DEFAULT);
+ mCallbacks.closeAfterDelete();
}
}
@@ -1144,8 +1238,7 @@
Toast.makeText(mContext, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
}
- // TODO: How do we get rid of the dialog
-// dismissDialog(progress);
+ if (progress != null) progress.dismiss();
// Stop the service that was protecting us
mContext.stopService(new Intent(mContext, EmptyService.class));
@@ -1191,4 +1284,46 @@
super.onRestoreInstanceState(savedInstanceState);
}
+
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ // Ignore failed requests
+ if (resultCode != Activity.RESULT_OK) return;
+ switch (requestCode) {
+ case R.id.edit_request_code_photo_picked_with_data: {
+ BaseContactEditorView requestingEditor = null;
+ for (int i = 0; i < mContent.getChildCount(); i++) {
+ View childView = mContent.getChildAt(i);
+ if (childView instanceof BaseContactEditorView) {
+ BaseContactEditorView editor = (BaseContactEditorView) childView;
+ if (editor.getRawContactId() == mRawContactIdRequestingPhoto) {
+ requestingEditor = editor;
+ break;
+ }
+ }
+ }
+
+ if (requestingEditor != null) {
+ final Bitmap photo = data.getParcelableExtra("data");
+ requestingEditor.setPhotoBitmap(photo);
+ mRawContactIdRequestingPhoto = -1;
+ } else {
+ // The contact that requested the photo is no longer present.
+ // TODO: Show error message
+ }
+
+ break;
+ }
+
+ case R.id.edit_request_code_camera_with_data: {
+ doCropPhoto(mCurrentPhotoFile);
+ break;
+ }
+ case R.id.edit_request_code_join: {
+ if (data != null) {
+ final long contactId = ContentUris.parseId(data.getData());
+ joinAggregate(contactId);
+ }
+ }
+ }
+ }
}