| /* |
| * Copyright (C) 2009 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.ui; |
| |
| import com.android.contacts.ContactsSearchManager; |
| import com.android.contacts.ContactsUtils; |
| import com.android.contacts.JoinContactActivity; |
| import com.android.contacts.R; |
| import com.android.contacts.model.ContactsSource; |
| import com.android.contacts.model.Editor; |
| import com.android.contacts.model.EntityDelta; |
| import com.android.contacts.model.EntityModifier; |
| import com.android.contacts.model.EntitySet; |
| import com.android.contacts.model.GoogleSource; |
| import com.android.contacts.model.Sources; |
| import com.android.contacts.model.ContactsSource.EditType; |
| import com.android.contacts.model.Editor.EditorListener; |
| import com.android.contacts.model.EntityDelta.ValuesDelta; |
| import com.android.contacts.ui.widget.BaseContactEditorView; |
| import com.android.contacts.ui.widget.PhotoEditorView; |
| import com.android.contacts.util.DialogManager; |
| import com.android.contacts.util.EmptyService; |
| import com.android.contacts.util.WeakAsyncTask; |
| |
| import android.accounts.Account; |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.app.Dialog; |
| import android.app.ProgressDialog; |
| import android.content.ActivityNotFoundException; |
| import android.content.ContentProviderOperation; |
| import android.content.ContentProviderResult; |
| import android.content.ContentResolver; |
| import android.content.ContentUris; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Entity; |
| import android.content.Intent; |
| import android.content.OperationApplicationException; |
| 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.Bundle; |
| import android.os.Environment; |
| import android.os.RemoteException; |
| import android.provider.ContactsContract; |
| import android.provider.MediaStore; |
| import android.provider.ContactsContract.AggregationExceptions; |
| import android.provider.ContactsContract.Contacts; |
| import android.provider.ContactsContract.RawContacts; |
| import android.provider.ContactsContract.CommonDataKinds.Email; |
| import android.provider.ContactsContract.CommonDataKinds.Phone; |
| import android.provider.ContactsContract.Contacts.Data; |
| 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.widget.ArrayAdapter; |
| import android.widget.LinearLayout; |
| import android.widget.ListAdapter; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| |
| import java.io.File; |
| import java.lang.ref.WeakReference; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Date; |
| |
| /** |
| * Activity for editing or inserting a contact. |
| */ |
| public final class EditContactActivity extends Activity |
| implements View.OnClickListener, Comparator<EntityDelta>, |
| DialogManager.DialogShowingViewActivity { |
| |
| private static final String TAG = "EditContactActivity"; |
| |
| /** 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"; |
| private static final String KEY_CURRENT_PHOTO_FILE = "currentphotofile"; |
| private static final String KEY_QUERY_SELECTION = "queryselection"; |
| private static final String KEY_CONTACT_ID_FOR_JOIN = "contactidforjoin"; |
| |
| /** The result code when view activity should close after edit returns */ |
| public static final int RESULT_CLOSE_VIEW_ACTIVITY = 777; |
| |
| 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 static final int DIALOG_CONFIRM_DELETE = 1; |
| private static final int DIALOG_CONFIRM_READONLY_DELETE = 2; |
| private static final int DIALOG_CONFIRM_MULTIPLE_DELETE = 3; |
| private static final int DIALOG_CONFIRM_READONLY_HIDE = 4; |
| private static final int DIALOG_PICK_PHOTO = 5; |
| private static final int DIALOG_SPLIT = 6; |
| private static final int DIALOG_SELECT_ACCOUNT = 7; |
| private static final int DIALOG_VIEW_DIALOGS_ID1 = 8; |
| private static final int DIALOG_VIEW_DIALOGS_ID2 = 9; |
| |
| 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; |
| |
| String mQuerySelection; |
| |
| private long mContactIdForJoin; |
| |
| private static final int STATUS_LOADING = 0; |
| private static final int STATUS_EDITING = 1; |
| private static final int STATUS_SAVING = 2; |
| |
| private int mStatus; |
| private DialogManager mDialogManager; |
| |
| EntitySet mState; |
| |
| /** The linear layout holding the ContactEditorViews */ |
| LinearLayout mContent; |
| |
| private ViewIdGenerator mViewIdGenerator; |
| |
| @Override |
| protected void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| |
| final Intent intent = getIntent(); |
| final String action = intent.getAction(); |
| |
| setContentView(R.layout.act_edit); |
| |
| mDialogManager = new DialogManager(this, DIALOG_VIEW_DIALOGS_ID1, DIALOG_VIEW_DIALOGS_ID2); |
| |
| // Build editor and listen for photo requests |
| mContent = (LinearLayout) findViewById(R.id.editors); |
| |
| findViewById(R.id.btn_done).setOnClickListener(this); |
| findViewById(R.id.btn_discard).setOnClickListener(this); |
| |
| // Handle initial actions only when existing state missing |
| final boolean hasIncomingState = icicle != null && icicle.containsKey(KEY_EDIT_STATE); |
| |
| if (Intent.ACTION_EDIT.equals(action) && !hasIncomingState) { |
| setTitle(R.string.editContact_title_edit); |
| mStatus = STATUS_LOADING; |
| |
| // Read initial state from database |
| new QueryEntitiesTask(this).execute(intent); |
| } else if (Intent.ACTION_INSERT.equals(action) && !hasIncomingState) { |
| setTitle(R.string.editContact_title_insert); |
| mStatus = STATUS_EDITING; |
| // Trigger dialog to pick account type |
| doAddAction(); |
| } |
| |
| if (icicle == null) { |
| // If icicle is non-null, onRestoreInstanceState() will restore the generator. |
| mViewIdGenerator = new ViewIdGenerator(); |
| } |
| } |
| |
| private static class QueryEntitiesTask extends |
| WeakAsyncTask<Intent, Void, EntitySet, EditContactActivity> { |
| |
| private String mSelection; |
| |
| public QueryEntitiesTask(EditContactActivity target) { |
| super(target); |
| } |
| |
| @Override |
| protected EntitySet doInBackground(EditContactActivity target, Intent... params) { |
| final Intent intent = params[0]; |
| |
| final ContentResolver resolver = target.getContentResolver(); |
| |
| // Handle both legacy and new authorities |
| final Uri data = intent.getData(); |
| final String authority = data.getAuthority(); |
| final String mimeType = intent.resolveType(resolver); |
| |
| mSelection = "0"; |
| if (ContactsContract.AUTHORITY.equals(authority)) { |
| if (Contacts.CONTENT_ITEM_TYPE.equals(mimeType)) { |
| // Handle selected aggregate |
| final long contactId = ContentUris.parseId(data); |
| mSelection = RawContacts.CONTACT_ID + "=" + contactId; |
| } else if (RawContacts.CONTENT_ITEM_TYPE.equals(mimeType)) { |
| final long rawContactId = ContentUris.parseId(data); |
| final long contactId = ContactsUtils.queryForContactId(resolver, rawContactId); |
| mSelection = RawContacts.CONTACT_ID + "=" + contactId; |
| } |
| } else if (android.provider.Contacts.AUTHORITY.equals(authority)) { |
| final long rawContactId = ContentUris.parseId(data); |
| mSelection = Data.RAW_CONTACT_ID + "=" + rawContactId; |
| } |
| |
| return EntitySet.fromQuery(target.getContentResolver(), mSelection, null, null); |
| } |
| |
| @Override |
| protected void onPostExecute(EditContactActivity target, EntitySet entitySet) { |
| target.mQuerySelection = mSelection; |
| |
| // Load edit details in background |
| final Context context = target; |
| final Sources sources = Sources.getInstance(context); |
| |
| // Handle any incoming values that should be inserted |
| final Bundle extras = target.getIntent().getExtras(); |
| final boolean hasExtras = extras != null && extras.size() > 0; |
| final boolean hasState = entitySet.size() > 0; |
| if (hasExtras && hasState) { |
| // Find source defining the first RawContact found |
| final EntityDelta state = entitySet.get(0); |
| final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE); |
| final ContactsSource source = sources.getInflatedSource(accountType, |
| ContactsSource.LEVEL_CONSTRAINTS); |
| EntityModifier.parseExtras(context, source, state, extras); |
| } |
| |
| target.mState = entitySet; |
| |
| // Bind UI to new background state |
| target.bindEditors(); |
| } |
| } |
| |
| @Override |
| protected void onSaveInstanceState(Bundle outState) { |
| if (hasValidState()) { |
| // Store entities with modifications |
| outState.putParcelable(KEY_EDIT_STATE, mState); |
| } |
| |
| outState.putLong(KEY_RAW_CONTACT_ID_REQUESTING_PHOTO, mRawContactIdRequestingPhoto); |
| outState.putParcelable(KEY_VIEW_ID_GENERATOR, mViewIdGenerator); |
| if (mCurrentPhotoFile != null) { |
| outState.putString(KEY_CURRENT_PHOTO_FILE, mCurrentPhotoFile.toString()); |
| } |
| outState.putString(KEY_QUERY_SELECTION, mQuerySelection); |
| outState.putLong(KEY_CONTACT_ID_FOR_JOIN, mContactIdForJoin); |
| super.onSaveInstanceState(outState); |
| } |
| |
| @Override |
| protected void onRestoreInstanceState(Bundle savedInstanceState) { |
| // Read modifications from instance |
| mState = savedInstanceState.<EntitySet> getParcelable(KEY_EDIT_STATE); |
| mRawContactIdRequestingPhoto = savedInstanceState.getLong( |
| KEY_RAW_CONTACT_ID_REQUESTING_PHOTO); |
| mViewIdGenerator = savedInstanceState.getParcelable(KEY_VIEW_ID_GENERATOR); |
| String fileName = savedInstanceState.getString(KEY_CURRENT_PHOTO_FILE); |
| if (fileName != null) { |
| mCurrentPhotoFile = new File(fileName); |
| } |
| mQuerySelection = savedInstanceState.getString(KEY_QUERY_SELECTION); |
| mContactIdForJoin = savedInstanceState.getLong(KEY_CONTACT_ID_FOR_JOIN); |
| |
| bindEditors(); |
| |
| super.onRestoreInstanceState(savedInstanceState); |
| } |
| |
| @Override |
| protected Dialog onCreateDialog(int id, Bundle bundle) { |
| switch (id) { |
| case DIALOG_CONFIRM_DELETE: |
| return new AlertDialog.Builder(this) |
| .setTitle(R.string.deleteConfirmation_title) |
| .setIcon(android.R.drawable.ic_dialog_alert) |
| .setMessage(R.string.deleteConfirmation) |
| .setNegativeButton(android.R.string.cancel, null) |
| .setPositiveButton(android.R.string.ok, new DeleteClickListener()) |
| .setCancelable(false) |
| .create(); |
| case DIALOG_CONFIRM_READONLY_DELETE: |
| return new AlertDialog.Builder(this) |
| .setTitle(R.string.deleteConfirmation_title) |
| .setIcon(android.R.drawable.ic_dialog_alert) |
| .setMessage(R.string.readOnlyContactDeleteConfirmation) |
| .setNegativeButton(android.R.string.cancel, null) |
| .setPositiveButton(android.R.string.ok, new DeleteClickListener()) |
| .setCancelable(false) |
| .create(); |
| case DIALOG_CONFIRM_MULTIPLE_DELETE: |
| return new AlertDialog.Builder(this) |
| .setTitle(R.string.deleteConfirmation_title) |
| .setIcon(android.R.drawable.ic_dialog_alert) |
| .setMessage(R.string.multipleContactDeleteConfirmation) |
| .setNegativeButton(android.R.string.cancel, null) |
| .setPositiveButton(android.R.string.ok, new DeleteClickListener()) |
| .setCancelable(false) |
| .create(); |
| case DIALOG_CONFIRM_READONLY_HIDE: |
| return new AlertDialog.Builder(this) |
| .setTitle(R.string.deleteConfirmation_title) |
| .setIcon(android.R.drawable.ic_dialog_alert) |
| .setMessage(R.string.readOnlyContactWarning) |
| .setPositiveButton(android.R.string.ok, new DeleteClickListener()) |
| .setCancelable(false) |
| .create(); |
| case DIALOG_PICK_PHOTO: |
| return createPickPhotoDialog(); |
| case DIALOG_SPLIT: |
| return createSplitDialog(); |
| case DIALOG_SELECT_ACCOUNT: |
| return createSelectAccountDialog(bundle); |
| default: |
| return mDialogManager.onCreateDialog(id, bundle); |
| } |
| } |
| |
| /** |
| * Dismiss the given {@link Dialog}. |
| */ |
| static void dismissDialog(Dialog dialog) { |
| try { |
| // Only dismiss when valid reference and still showing |
| if (dialog != null && dialog.isShowing()) { |
| dialog.dismiss(); |
| } |
| } catch (Exception e) { |
| Log.w(TAG, "Ignoring exception while dismissing dialog: " + e.toString()); |
| } |
| } |
| |
| /** |
| * Check if our internal {@link #mState} is valid, usually checked before |
| * performing user actions. |
| */ |
| protected boolean hasValidState() { |
| return mStatus == STATUS_EDITING && mState != null && mState.size() > 0; |
| } |
| |
| /** |
| * Rebuild the editors to match our underlying {@link #mState} object, usually |
| * called once we've parsed {@link Entity} data or have inserted a new |
| * {@link RawContacts}. |
| */ |
| protected void bindEditors() { |
| if (mState == null) { |
| return; |
| } |
| |
| final LayoutInflater inflater = (LayoutInflater) getSystemService( |
| Context.LAYOUT_INFLATER_SERVICE); |
| final Sources sources = Sources.getInstance(this); |
| |
| // Sort the editors |
| Collections.sort(mState, this); |
| |
| // Remove any existing editors and rebuild any visible |
| mContent.removeAllViews(); |
| int size = mState.size(); |
| for (int i = 0; i < size; i++) { |
| // TODO ensure proper ordering of entities in the list |
| EntityDelta entity = mState.get(i); |
| final ValuesDelta values = entity.getValues(); |
| if (!values.isVisible()) continue; |
| |
| final String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE); |
| final ContactsSource source = sources.getInflatedSource(accountType, |
| ContactsSource.LEVEL_CONSTRAINTS); |
| final long rawContactId = values.getAsLong(RawContacts._ID); |
| |
| BaseContactEditorView editor; |
| if (!source.readOnly) { |
| editor = (BaseContactEditorView) inflater.inflate(R.layout.item_contact_editor, |
| mContent, false); |
| } else { |
| editor = (BaseContactEditorView) inflater.inflate( |
| R.layout.item_read_only_contact_editor, mContent, false); |
| } |
| PhotoEditorView photoEditor = editor.getPhotoEditor(); |
| photoEditor.setEditorListener(new PhotoListener(rawContactId, source.readOnly, |
| photoEditor)); |
| |
| mContent.addView(editor); |
| editor.setState(entity, source, mViewIdGenerator); |
| } |
| |
| // Show editor now that we've loaded state |
| mContent.setVisibility(View.VISIBLE); |
| mStatus = STATUS_EDITING; |
| } |
| |
| /** |
| * Class that listens to requests coming from photo editors |
| */ |
| private class PhotoListener implements EditorListener, DialogInterface.OnClickListener { |
| private long mRawContactId; |
| private boolean mReadOnly; |
| private PhotoEditorView mEditor; |
| |
| public PhotoListener(long rawContactId, boolean readOnly, PhotoEditorView editor) { |
| mRawContactId = rawContactId; |
| mReadOnly = readOnly; |
| mEditor = editor; |
| } |
| |
| public void onDeleted(Editor editor) { |
| // Do nothing |
| } |
| |
| public void onRequest(int request) { |
| if (!hasValidState()) return; |
| |
| if (request == EditorListener.REQUEST_PICK_PHOTO) { |
| if (mEditor.hasSetPhoto()) { |
| // There is an existing photo, offer to remove, replace, or promoto to primary |
| createPhotoDialog().show(); |
| } else if (!mReadOnly) { |
| // No photo set and not read-only, try to set the photo |
| doPickPhotoAction(mRawContactId); |
| } |
| } |
| } |
| |
| /** |
| * Prepare dialog for picking a new {@link EditType} or entering a |
| * custom label. This dialog is limited to the valid types as determined |
| * by {@link EntityModifier}. |
| */ |
| public Dialog createPhotoDialog() { |
| Context context = EditContactActivity.this; |
| |
| // Wrap our context to inflate list items using correct theme |
| final Context dialogContext = new ContextThemeWrapper(context, |
| android.R.style.Theme_Light); |
| |
| String[] choices; |
| if (mReadOnly) { |
| choices = new String[1]; |
| choices[0] = getString(R.string.use_photo_as_primary); |
| } else { |
| choices = new String[3]; |
| choices[0] = getString(R.string.use_photo_as_primary); |
| choices[1] = getString(R.string.removePicture); |
| choices[2] = getString(R.string.changePicture); |
| } |
| final ListAdapter adapter = new ArrayAdapter<String>(dialogContext, |
| android.R.layout.simple_list_item_1, choices); |
| |
| final AlertDialog.Builder builder = new AlertDialog.Builder(dialogContext); |
| builder.setTitle(R.string.attachToContact); |
| builder.setSingleChoiceItems(adapter, -1, this); |
| return builder.create(); |
| } |
| |
| /** |
| * Called when something in the dialog is clicked |
| */ |
| public void onClick(DialogInterface dialog, int which) { |
| dialog.dismiss(); |
| |
| switch (which) { |
| case 0: |
| // Set the photo as super primary |
| mEditor.setSuperPrimary(true); |
| |
| // And set all other photos as not super primary |
| int count = mContent.getChildCount(); |
| for (int i = 0; i < count; i++) { |
| View childView = mContent.getChildAt(i); |
| if (childView instanceof BaseContactEditorView) { |
| BaseContactEditorView editor = (BaseContactEditorView) childView; |
| PhotoEditorView photoEditor = editor.getPhotoEditor(); |
| if (!photoEditor.equals(mEditor)) { |
| photoEditor.setSuperPrimary(false); |
| } |
| } |
| } |
| break; |
| |
| case 1: |
| // Remove the photo |
| mEditor.setPhotoBitmap(null); |
| break; |
| |
| case 2: |
| // Pick a new photo for the contact |
| doPickPhotoAction(mRawContactId); |
| break; |
| } |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void onClick(View view) { |
| switch (view.getId()) { |
| case R.id.btn_done: |
| doSaveAction(SAVE_MODE_DEFAULT); |
| break; |
| case R.id.btn_discard: |
| doRevertAction(); |
| break; |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public void onBackPressed() { |
| doSaveAction(SAVE_MODE_DEFAULT); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| protected void onActivityResult(int requestCode, int resultCode, Intent data) { |
| // Ignore failed requests |
| if (resultCode != RESULT_OK) return; |
| |
| switch (requestCode) { |
| case 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 CAMERA_WITH_DATA: { |
| doCropPhoto(mCurrentPhotoFile); |
| break; |
| } |
| |
| case REQUEST_JOIN_CONTACT: { |
| if (resultCode == RESULT_OK && data != null) { |
| final long contactId = ContentUris.parseId(data.getData()); |
| joinAggregate(contactId); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public boolean onCreateOptionsMenu(Menu menu) { |
| super.onCreateOptionsMenu(menu); |
| |
| MenuInflater inflater = getMenuInflater(); |
| inflater.inflate(R.menu.edit, menu); |
| |
| |
| return true; |
| } |
| |
| @Override |
| public boolean onPrepareOptionsMenu(Menu menu) { |
| menu.findItem(R.id.menu_split).setVisible(mState != null && mState.size() > 1); |
| return true; |
| } |
| |
| @Override |
| 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; |
| } |
| |
| /** |
| * Background task for persisting edited contact data, using the changes |
| * defined by a set of {@link EntityDelta}. This task starts |
| * {@link EmptyService} to make sure the background thread can finish |
| * persisting in cases where the system wants to reclaim our process. |
| */ |
| public static class PersistTask extends |
| WeakAsyncTask<EntitySet, Void, Integer, EditContactActivity> { |
| private static final int PERSIST_TRIES = 3; |
| |
| private static final int RESULT_UNCHANGED = 0; |
| private static final int RESULT_SUCCESS = 1; |
| private static final int RESULT_FAILURE = 2; |
| |
| private WeakReference<ProgressDialog> mProgress; |
| |
| private int mSaveMode; |
| private Uri mContactLookupUri = null; |
| |
| public PersistTask(EditContactActivity target, int saveMode) { |
| super(target); |
| mSaveMode = saveMode; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| protected void onPreExecute(EditContactActivity target) { |
| mProgress = new WeakReference<ProgressDialog>(ProgressDialog.show(target, null, |
| target.getText(R.string.savingContact))); |
| |
| // Before starting this task, start an empty service to protect our |
| // process from being reclaimed by the system. |
| final Context context = target; |
| context.startService(new Intent(context, EmptyService.class)); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| protected Integer doInBackground(EditContactActivity target, EntitySet... params) { |
| final Context context = target; |
| final ContentResolver resolver = context.getContentResolver(); |
| |
| EntitySet state = params[0]; |
| |
| // Trim any empty fields, and RawContacts, before persisting |
| final Sources sources = Sources.getInstance(context); |
| EntityModifier.trimEmpty(state, sources); |
| |
| // Attempt to persist changes |
| int tries = 0; |
| Integer result = RESULT_FAILURE; |
| while (tries++ < PERSIST_TRIES) { |
| try { |
| // Build operations and try applying |
| final ArrayList<ContentProviderOperation> diff = state.buildDiff(); |
| ContentProviderResult[] results = null; |
| if (!diff.isEmpty()) { |
| results = resolver.applyBatch(ContactsContract.AUTHORITY, diff); |
| } |
| |
| final long rawContactId = getRawContactId(state, diff, results); |
| if (rawContactId != -1) { |
| final Uri rawContactUri = ContentUris.withAppendedId( |
| RawContacts.CONTENT_URI, rawContactId); |
| |
| // convert the raw contact URI to a contact URI |
| mContactLookupUri = RawContacts.getContactLookupUri(resolver, |
| rawContactUri); |
| } |
| result = (diff.size() > 0) ? RESULT_SUCCESS : RESULT_UNCHANGED; |
| break; |
| |
| } catch (RemoteException e) { |
| // Something went wrong, bail without success |
| Log.e(TAG, "Problem persisting user edits", e); |
| break; |
| |
| } catch (OperationApplicationException e) { |
| // Version consistency failed, re-parent change and try again |
| Log.w(TAG, "Version consistency failed, re-parenting: " + e.toString()); |
| final EntitySet newState = EntitySet.fromQuery(resolver, |
| target.mQuerySelection, null, null); |
| state = EntitySet.mergeAfter(newState, state); |
| } |
| } |
| |
| return result; |
| } |
| |
| private long getRawContactId(EntitySet state, |
| final ArrayList<ContentProviderOperation> diff, |
| final ContentProviderResult[] results) { |
| long rawContactId = state.findRawContactId(); |
| if (rawContactId != -1) { |
| return rawContactId; |
| } |
| |
| // we gotta do some searching for the id |
| final int diffSize = diff.size(); |
| for (int i = 0; i < diffSize; i++) { |
| ContentProviderOperation operation = diff.get(i); |
| if (operation.getType() == ContentProviderOperation.TYPE_INSERT |
| && operation.getUri().getEncodedPath().contains( |
| RawContacts.CONTENT_URI.getEncodedPath())) { |
| return ContentUris.parseId(results[i].uri); |
| } |
| } |
| return -1; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| protected void onPostExecute(EditContactActivity target, Integer result) { |
| final Context context = target; |
| final ProgressDialog progress = mProgress.get(); |
| |
| if (result == RESULT_SUCCESS && mSaveMode != SAVE_MODE_JOIN) { |
| Toast.makeText(context, R.string.contactSavedToast, Toast.LENGTH_SHORT).show(); |
| } else if (result == RESULT_FAILURE) { |
| Toast.makeText(context, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show(); |
| } |
| |
| dismissDialog(progress); |
| |
| // Stop the service that was protecting us |
| context.stopService(new Intent(context, EmptyService.class)); |
| |
| target.onSaveCompleted(result != RESULT_FAILURE, mSaveMode, mContactLookupUri); |
| } |
| } |
| |
| /** |
| * Saves or creates the contact based on the mode, and if successful |
| * finishes the activity. |
| */ |
| boolean doSaveAction(int saveMode) { |
| if (!hasValidState()) { |
| return false; |
| } |
| |
| mStatus = STATUS_SAVING; |
| final PersistTask task = new PersistTask(this, saveMode); |
| task.execute(mState); |
| |
| return true; |
| } |
| |
| private class DeleteClickListener implements DialogInterface.OnClickListener { |
| |
| public void onClick(DialogInterface dialog, int which) { |
| Sources sources = Sources.getInstance(EditContactActivity.this); |
| // Mark all raw contacts for deletion |
| for (EntityDelta delta : mState) { |
| delta.markDeleted(); |
| } |
| // Save the deletes |
| doSaveAction(SAVE_MODE_DEFAULT); |
| finish(); |
| } |
| } |
| |
| private void onSaveCompleted(boolean success, int saveMode, Uri contactLookupUri) { |
| switch (saveMode) { |
| case SAVE_MODE_DEFAULT: |
| if (success && contactLookupUri != null) { |
| final Intent resultIntent = new Intent(); |
| |
| final Uri requestData = getIntent().getData(); |
| final String requestAuthority = requestData == null ? null : requestData |
| .getAuthority(); |
| |
| if (android.provider.Contacts.AUTHORITY.equals(requestAuthority)) { |
| // Build legacy Uri when requested by caller |
| final long contactId = ContentUris.parseId(Contacts.lookupContact( |
| getContentResolver(), contactLookupUri)); |
| final Uri legacyUri = ContentUris.withAppendedId( |
| android.provider.Contacts.People.CONTENT_URI, contactId); |
| resultIntent.setData(legacyUri); |
| } else { |
| // Otherwise pass back a lookup-style Uri |
| resultIntent.setData(contactLookupUri); |
| } |
| |
| setResult(RESULT_OK, resultIntent); |
| } else { |
| setResult(RESULT_CANCELED, null); |
| } |
| finish(); |
| 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; |
| } |
| } |
| |
| /** |
| * Shows a list of aggregates that can be joined into the currently viewed aggregate. |
| * |
| * @param contactLookupUri the fresh URI for the currently edited contact (after saving it) |
| */ |
| public void showJoinAggregateActivity(Uri contactLookupUri) { |
| if (contactLookupUri == null) { |
| return; |
| } |
| |
| mContactIdForJoin = ContentUris.parseId(contactLookupUri); |
| Intent intent = new Intent(JoinContactActivity.JOIN_CONTACT); |
| intent.putExtra(JoinContactActivity.EXTRA_TARGET_CONTACT_ID, mContactIdForJoin); |
| startActivityForResult(intent, REQUEST_JOIN_CONTACT); |
| } |
| |
| private interface JoinContactQuery { |
| String[] PROJECTION = { |
| RawContacts._ID, |
| RawContacts.CONTACT_ID, |
| RawContacts.NAME_VERIFIED, |
| }; |
| |
| String SELECTION = RawContacts.CONTACT_ID + "=? OR " + RawContacts.CONTACT_ID + "=?"; |
| |
| int _ID = 0; |
| int CONTACT_ID = 1; |
| int NAME_VERIFIED = 2; |
| } |
| |
| /** |
| * Performs aggregation with the contact selected by the user from suggestions or A-Z list. |
| */ |
| private void joinAggregate(final long contactId) { |
| ContentResolver resolver = getContentResolver(); |
| |
| // Load raw contact IDs for all raw contacts involved - currently edited and selected |
| // in the join UIs |
| Cursor c = resolver.query(RawContacts.CONTENT_URI, |
| JoinContactQuery.PROJECTION, |
| JoinContactQuery.SELECTION, |
| new String[]{String.valueOf(contactId), String.valueOf(mContactIdForJoin)}, null); |
| |
| long rawContactIds[]; |
| long verifiedNameRawContactId = -1; |
| try { |
| rawContactIds = new long[c.getCount()]; |
| for (int i = 0; i < rawContactIds.length; i++) { |
| c.moveToNext(); |
| long rawContactId = c.getLong(JoinContactQuery._ID); |
| rawContactIds[i] = rawContactId; |
| if (c.getLong(JoinContactQuery.CONTACT_ID) == mContactIdForJoin) { |
| if (verifiedNameRawContactId == -1 |
| || c.getInt(JoinContactQuery.NAME_VERIFIED) != 0) { |
| verifiedNameRawContactId = rawContactId; |
| } |
| } |
| } |
| } finally { |
| c.close(); |
| } |
| |
| // For each pair of raw contacts, insert an aggregation exception |
| ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(); |
| for (int i = 0; i < rawContactIds.length; i++) { |
| for (int j = 0; j < rawContactIds.length; j++) { |
| if (i != j) { |
| buildJoinContactDiff(operations, rawContactIds[i], rawContactIds[j]); |
| } |
| } |
| } |
| |
| // Mark the original contact as "name verified" to make sure that the contact |
| // display name does not change as a result of the join |
| Builder builder = ContentProviderOperation.newUpdate( |
| ContentUris.withAppendedId(RawContacts.CONTENT_URI, verifiedNameRawContactId)); |
| builder.withValue(RawContacts.NAME_VERIFIED, 1); |
| operations.add(builder.build()); |
| |
| // Apply all aggregation exceptions as one batch |
| try { |
| getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations); |
| |
| // We can use any of the constituent raw contacts to refresh the UI - why not the first |
| Intent intent = new Intent(); |
| intent.setData(ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactIds[0])); |
| |
| // Reload the new state from database |
| new QueryEntitiesTask(this).execute(intent); |
| |
| Toast.makeText(this, R.string.contactsJoinedMessage, Toast.LENGTH_LONG).show(); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Failed to apply aggregation exception batch", e); |
| Toast.makeText(this, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show(); |
| } catch (OperationApplicationException e) { |
| Log.e(TAG, "Failed to apply aggregation exception batch", e); |
| Toast.makeText(this, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show(); |
| } |
| } |
| |
| /** |
| * Construct a {@link AggregationExceptions#TYPE_KEEP_TOGETHER} ContentProviderOperation. |
| */ |
| private void buildJoinContactDiff(ArrayList<ContentProviderOperation> operations, |
| long rawContactId1, long rawContactId2) { |
| Builder builder = |
| ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI); |
| builder.withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER); |
| builder.withValue(AggregationExceptions.RAW_CONTACT_ID1, rawContactId1); |
| builder.withValue(AggregationExceptions.RAW_CONTACT_ID2, rawContactId2); |
| operations.add(builder.build()); |
| } |
| |
| /** |
| * Revert any changes the user has made, and finish the activity. |
| */ |
| private boolean doRevertAction() { |
| finish(); |
| return true; |
| } |
| |
| /** |
| * Create a new {@link RawContacts} which will exist as another |
| * {@link EntityDelta} under the currently edited {@link Contacts}. |
| */ |
| private boolean doAddAction() { |
| if (mStatus != STATUS_EDITING) { |
| return false; |
| } |
| |
| // Adding is okay when missing state |
| new AddContactTask(this).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; |
| Sources sources = Sources.getInstance(EditContactActivity.this); |
| 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) { |
| showDialog(DIALOG_CONFIRM_READONLY_DELETE); |
| } else if (readOnlySourcesCnt > 0 && writableSourcesCnt == 0) { |
| showDialog(DIALOG_CONFIRM_READONLY_HIDE); |
| } else if (readOnlySourcesCnt == 0 && writableSourcesCnt > 1) { |
| showDialog(DIALOG_CONFIRM_MULTIPLE_DELETE); |
| } else { |
| showDialog(DIALOG_CONFIRM_DELETE); |
| } |
| return true; |
| } |
| |
| /** |
| * Pick a specific photo to be added under the currently selected tab. |
| */ |
| boolean doPickPhotoAction(long rawContactId) { |
| if (!hasValidState()) return false; |
| |
| mRawContactIdRequestingPhoto = rawContactId; |
| |
| showDialog(DIALOG_PICK_PHOTO); |
| |
| return true; |
| } |
| |
| /** |
| * Creates a dialog offering two options: take a photo or pick a photo from the gallery. |
| */ |
| private Dialog createPickPhotoDialog() { |
| Context context = EditContactActivity.this; |
| |
| // Wrap our context to inflate list items using correct theme |
| final Context dialogContext = new ContextThemeWrapper(context, |
| android.R.style.Theme_Light); |
| |
| String[] choices = new String[2]; |
| choices[0] = getString(R.string.take_photo); |
| choices[1] = getString(R.string.pick_photo); |
| final ListAdapter adapter = new ArrayAdapter<String>(dialogContext, |
| android.R.layout.simple_list_item_1, choices); |
| |
| final AlertDialog.Builder builder = new AlertDialog.Builder(dialogContext); |
| builder.setTitle(R.string.attachToContact); |
| builder.setSingleChoiceItems(adapter, -1, new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int which) { |
| dialog.dismiss(); |
| switch(which) { |
| case 0: |
| doTakePhoto(); |
| break; |
| case 1: |
| doPickPhotoFromGallery(); |
| break; |
| } |
| } |
| }); |
| return builder.create(); |
| } |
| |
| /** |
| * Create a file name for the icon photo using current time. |
| */ |
| private String getPhotoFileName() { |
| Date date = new Date(System.currentTimeMillis()); |
| SimpleDateFormat dateFormat = new SimpleDateFormat("'IMG'_yyyyMMdd_HHmmss"); |
| return dateFormat.format(date) + ".jpg"; |
| } |
| |
| /** |
| * Launches Camera to take a picture and store it in a file. |
| */ |
| protected void doTakePhoto() { |
| try { |
| // Launch camera to take photo for selected contact |
| PHOTO_DIR.mkdirs(); |
| mCurrentPhotoFile = new File(PHOTO_DIR, getPhotoFileName()); |
| final Intent intent = getTakePickIntent(mCurrentPhotoFile); |
| startActivityForResult(intent, CAMERA_WITH_DATA); |
| } catch (ActivityNotFoundException e) { |
| Toast.makeText(this, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show(); |
| } |
| } |
| |
| /** |
| * Constructs an intent for capturing a photo and storing it in a temporary file. |
| */ |
| public static Intent getTakePickIntent(File f) { |
| Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE, null); |
| intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f)); |
| return intent; |
| } |
| |
| /** |
| * Sends a newly acquired photo to Gallery for cropping |
| */ |
| protected void doCropPhoto(File f) { |
| try { |
| |
| // Add the image to the media store |
| MediaScannerConnection.scanFile( |
| this, |
| new String[] { f.getAbsolutePath() }, |
| new String[] { null }, |
| null); |
| |
| // Launch gallery to crop the photo |
| final Intent intent = getCropImageIntent(Uri.fromFile(f)); |
| startActivityForResult(intent, PHOTO_PICKED_WITH_DATA); |
| } catch (Exception e) { |
| Log.e(TAG, "Cannot crop image", e); |
| Toast.makeText(this, 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; |
| } |
| |
| /** |
| * Launches Gallery to pick a photo. |
| */ |
| protected void doPickPhotoFromGallery() { |
| try { |
| // Launch picker to choose photo for selected contact |
| final Intent intent = getPhotoPickIntent(); |
| startActivityForResult(intent, PHOTO_PICKED_WITH_DATA); |
| } catch (ActivityNotFoundException e) { |
| Toast.makeText(this, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show(); |
| } |
| } |
| |
| /** |
| * Constructs an intent for picking a photo from Gallery, cropping it and returning the bitmap. |
| */ |
| public static Intent getPhotoPickIntent() { |
| Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null); |
| intent.setType("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; |
| } |
| |
| /** {@inheritDoc} */ |
| public void onDeleted(Editor editor) { |
| // Ignore any editor deletes |
| } |
| |
| private boolean doSplitContactAction() { |
| if (!hasValidState()) return false; |
| |
| showDialog(DIALOG_SPLIT); |
| return true; |
| } |
| |
| private Dialog createSplitDialog() { |
| final AlertDialog.Builder builder = new AlertDialog.Builder(this); |
| builder.setTitle(R.string.splitConfirmation_title); |
| builder.setIcon(android.R.drawable.ic_dialog_alert); |
| builder.setMessage(R.string.splitConfirmation); |
| builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { |
| public void onClick(DialogInterface dialog, int which) { |
| // Split the contacts |
| mState.splitRawContacts(); |
| doSaveAction(SAVE_MODE_SPLIT); |
| } |
| }); |
| builder.setNegativeButton(android.R.string.cancel, null); |
| builder.setCancelable(false); |
| return builder.create(); |
| } |
| |
| private boolean doJoinContactAction() { |
| 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, ArrayList<Account>, EditContactActivity> { |
| |
| public AddContactTask(EditContactActivity target) { |
| super(target); |
| } |
| |
| @Override |
| protected ArrayList<Account> doInBackground(final EditContactActivity target, |
| Void... params) { |
| return Sources.getInstance(target).getAccounts(true); |
| } |
| |
| @Override |
| 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. |
| } |
| |
| // 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. |
| } |
| |
| Bundle bundle = new Bundle(); |
| bundle.putParcelableArrayList(BUNDLE_SELECT_ACCOUNT_LIST, accounts); |
| showDialog(DIALOG_SELECT_ACCOUNT, bundle); |
| } |
| |
| private Dialog createSelectAccountDialog(Bundle bundle) { |
| final ArrayList<Account> accounts = bundle.getParcelableArrayList( |
| BUNDLE_SELECT_ACCOUNT_LIST); |
| // Wrap our context to inflate list items using correct theme |
| final Context dialogContext = new ContextThemeWrapper(this, android.R.style.Theme_Light); |
| final LayoutInflater dialogInflater = |
| (LayoutInflater)dialogContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
| |
| 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_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(EditContactActivity.this)); |
| |
| 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); |
| 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_new_contact_account); |
| builder.setSingleChoiceItems(accountAdapter, 0, clickListener); |
| builder.setOnCancelListener(cancelListener); |
| final Dialog result = builder.create(); |
| result.setOnDismissListener(new OnDismissListener() { |
| public void onDismiss(DialogInterface dialog) { |
| removeDialog(DIALOG_SELECT_ACCOUNT); |
| } |
| }); |
| return result; |
| } |
| |
| /** |
| * @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); |
| |
| 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(); |
| } |
| |
| /** |
| * Compare EntityDeltas for sorting the stack of editors. |
| */ |
| public int compare(EntityDelta one, EntityDelta two) { |
| // Check direct equality |
| if (one.equals(two)) { |
| return 0; |
| } |
| |
| final Sources sources = Sources.getInstance(this); |
| String accountType = one.getValues().getAsString(RawContacts.ACCOUNT_TYPE); |
| final ContactsSource oneSource = sources.getInflatedSource(accountType, |
| ContactsSource.LEVEL_SUMMARY); |
| accountType = two.getValues().getAsString(RawContacts.ACCOUNT_TYPE); |
| final ContactsSource twoSource = sources.getInflatedSource(accountType, |
| ContactsSource.LEVEL_SUMMARY); |
| |
| // Check read-only |
| if (oneSource.readOnly && !twoSource.readOnly) { |
| return 1; |
| } else if (twoSource.readOnly && !oneSource.readOnly) { |
| return -1; |
| } |
| |
| // Check account type |
| boolean skipAccountTypeCheck = false; |
| boolean oneIsGoogle = oneSource instanceof GoogleSource; |
| boolean twoIsGoogle = twoSource instanceof GoogleSource; |
| if (oneIsGoogle && !twoIsGoogle) { |
| return -1; |
| } else if (twoIsGoogle && !oneIsGoogle) { |
| return 1; |
| } else if (oneIsGoogle && twoIsGoogle){ |
| skipAccountTypeCheck = true; |
| } |
| |
| int value; |
| if (!skipAccountTypeCheck) { |
| value = oneSource.accountType.compareTo(twoSource.accountType); |
| if (value != 0) { |
| return value; |
| } |
| } |
| |
| // Check account name |
| ValuesDelta oneValues = one.getValues(); |
| String oneAccount = oneValues.getAsString(RawContacts.ACCOUNT_NAME); |
| if (oneAccount == null) oneAccount = ""; |
| ValuesDelta twoValues = two.getValues(); |
| String twoAccount = twoValues.getAsString(RawContacts.ACCOUNT_NAME); |
| if (twoAccount == null) twoAccount = ""; |
| value = oneAccount.compareTo(twoAccount); |
| if (value != 0) { |
| return value; |
| } |
| |
| // Both are in the same account, fall back to contact ID |
| Long oneId = oneValues.getAsLong(RawContacts._ID); |
| Long twoId = twoValues.getAsLong(RawContacts._ID); |
| if (oneId == null) { |
| return -1; |
| } else if (twoId == null) { |
| return 1; |
| } |
| |
| return (int)(oneId - twoId); |
| } |
| |
| @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); |
| } |
| } |
| |
| public DialogManager getDialogManager() { |
| return mDialogManager; |
| } |
| } |