com.silentcircle.contacts.editor.ContactEditorFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.silentcircle.contacts.editor.ContactEditorFragment.java

Source

/*
Copyright  2013-2014, Silent Circle, LLC.
All rights reserved.
    
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Any redistribution, use, or modification is done solely for personal 
  benefit and not for any commercial purpose or for monetary gain
* Redistributions of source code must retain the above copyright
  notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.
* Neither the name Silent Circle nor the names of its contributors may 
  be used to endorse or promote products derived from this software 
  without specific prior written permission.
    
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL SILENT CIRCLE, LLC BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/*
 * This  implementation is edited version of original Android sources.
 */

/*
 * 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.silentcircle.contacts.editor;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
// import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ListPopupWindow;
import android.widget.Toast;

import com.silentcircle.contacts.GroupMetaDataLoader;
import com.silentcircle.contacts.ScContactSaveService;
import com.silentcircle.contacts.activities.ScContactEditorActivity;
import com.silentcircle.contacts.detail.PhotoSelectionHandler;
import com.silentcircle.contacts.detail.PhotoSelectionHandler19;
import com.silentcircle.contacts.model.Contact;
import com.silentcircle.contacts.model.ContactLoader;
import com.silentcircle.contacts.model.RawContact;
import com.silentcircle.contacts.model.RawContactDelta;
import com.silentcircle.contacts.model.RawContactDeltaList;
import com.silentcircle.contacts.model.RawContactModifier;
import com.silentcircle.contacts.model.account.AccountType;
import com.silentcircle.contacts.utils.ContactPhotoUtils19;
import com.silentcircle.contacts.utils.HelpUtils;
import com.silentcircle.contacts.utils.PhoneNumberHelper;
import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.Email;
import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.SipAddress;
import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.Organization;
import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.Phone;
import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.Photo;
import com.silentcircle.silentcontacts.ScContactsContract.CommonDataKinds.StructuredPostal;
import com.silentcircle.silentcontacts.ScContactsContract.Groups;
import com.silentcircle.silentcontacts.ScContactsContract.RawContacts;
//import com.android.contacts.GroupMetaDataLoader;
import com.silentcircle.contacts.R;
//import com.android.contacts.activities.ContactEditorAccountsChangedActivity;
//import com.android.contacts.activities.JoinContactActivity;
//import com.android.contacts.editor.AggregationSuggestionEngine.Suggestion;
import com.silentcircle.contacts.model.AccountTypeManager;
// import com.android.contacts.model.account.AccountWithDataSet;
// import com.android.contacts.model.account.GoogleAccountType;
//import com.silentcircle.silentcontacts.utils.AccountsListAdapter;
//import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
import com.silentcircle.contacts.utils.ContactPhotoUtils;

import com.actionbarsherlock.app.SherlockFragment;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Collections;
import java.util.Comparator;

public class ContactEditorFragment
        extends SherlockFragment /* implements SplitContactConfirmationDialogFragment.Listener,
                                 AggregationSuggestionEngine.Listener, AggregationSuggestionView.Listener, */
{

    private static final String TAG = ContactEditorFragment.class.getSimpleName();

    private static final int LOADER_DATA = 1;
    private static final int LOADER_GROUPS = 2;

    private static final String KEY_URI = "uri";
    private static final String KEY_ACTION = "action";
    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_CURRENT_PHOTO_URI = "currentphotouri";
    private static final String KEY_CONTACT_ID_FOR_JOIN = "contactidforjoin";
    private static final String KEY_CONTACT_WRITABLE_FOR_JOIN = "contactwritableforjoin";
    private static final String KEY_SHOW_JOIN_SUGGESTIONS = "showJoinSuggestions";
    private static final String KEY_ENABLED = "enabled";
    private static final String KEY_STATUS = "status";
    private static final String KEY_NEW_LOCAL_PROFILE = "newLocalProfile";
    private static final String KEY_IS_USER_PROFILE = "isUserProfile";
    private static final String KEY_UPDATED_PHOTOS = "updatedPhotos";

    public static final String SAVE_MODE_EXTRA_KEY = "saveMode";

    /**
     * An intent extra that forces the editor to add the edited contact
     * to the default group (e.g. "My Contacts").
     */
    public static final String INTENT_EXTRA_ADD_TO_DEFAULT_DIRECTORY = "addToDefaultDirectory";

    public static final String INTENT_EXTRA_NEW_LOCAL_PROFILE = "newLocalProfile";

    /**
     * Modes that specify what the AsyncTask has to perform after saving
     */
    public interface SaveMode {
        /**
         * Close the editor after saving
         */
        public static final int CLOSE = 0;

        /**
         * Reload the data so that the user can continue editing
         */
        public static final int RELOAD = 1;

        /**
         * Split the contact after saving
         */
        public static final int SPLIT = 2;

        /**
         * Join another contact after saving
         */
        public static final int JOIN = 3;

        /**
         * Navigate to Contacts Home activity after saving.
         */
        public static final int HOME = 4;
    }

    private interface Status {
        /**
         * The loader is fetching data
         */
        public static final int LOADING = 0;

        /**
         * Not currently busy. We are waiting for the user to enter data
         */
        public static final int EDITING = 1;

        /**
         * The data is currently being saved. This is used to prevent more
         * auto-saves (they shouldn't overlap)
         */
        public static final int SAVING = 2;

        /**
         * Prevents any more saves. This is used if in the following cases:
         * - After Save/Close
         * - After Revert
         * - After the user has accepted an edit suggestion
         */
        public static final int CLOSING = 3;

        /**
         * Prevents saving while running a child activity.
         */
        public static final int SUB_ACTIVITY = 4;
    }

    private static final int REQUEST_CODE_JOIN = 0;
    private static final int REQUEST_CODE_ACCOUNTS_CHANGED = 1;

    /**
     * The raw contact for which we started "take photo" or "choose photo from gallery" most
     * recently.  Used to restore {@link #mCurrentPhotoHandler} after orientation change.
     */
    private long mRawContactIdRequestingPhoto;
    /**
     * The {@link PhotoHandler} for the photo editor for the {@link #mRawContactIdRequestingPhoto}
     * raw contact.
     *
     * A {@link PhotoHandler} is created for each photo editor in {@link #bindPhotoHandler}, but
     * the only "active" one should get the activity result.  This member represents the active
     * one.
     */
    private PhotoHandler mCurrentPhotoHandler;
    private PhotoHandler19 mCurrentPhotoHandler19;

    private final EntityDeltaComparator mComparator = new EntityDeltaComparator();

    private Cursor mGroupMetaData;

    private String mCurrentPhotoFile;
    private Uri mCurrentPhotoUri;
    private Bundle mUpdatedPhotos = new Bundle();

    private Context mContext;
    private String mAction;
    private Uri mLookupUri;
    private Bundle mIntentExtras;
    private Listener mListener;

    private long mContactIdForJoin;
    private boolean mContactWritableForJoin;

    private ContactEditorUtils mEditorUtils;

    private LinearLayout mContent;
    private RawContactDeltaList mState;

    private ViewIdGenerator mViewIdGenerator;

    private long mLoaderStartTime;

    private int mStatus;

    //    private AggregationSuggestionEngine mAggregationSuggestionEngine;
    private long mAggregationSuggestionsRawContactId;
    private View mAggregationSuggestionView;

    private ListPopupWindow mAggregationSuggestionPopup;

    //    private static final class AggregationSuggestionAdapter extends BaseAdapter {
    //        private final Activity mActivity;
    //        private final boolean mSetNewContact;
    //        private final AggregationSuggestionView.Listener mListener;
    //        private final List<Suggestion> mSuggestions;
    //
    //        public AggregationSuggestionAdapter(Activity activity, boolean setNewContact,
    //                AggregationSuggestionView.Listener listener, List<Suggestion> suggestions) {
    //            mActivity = activity;
    //            mSetNewContact = setNewContact;
    //            mListener = listener;
    //            mSuggestions = suggestions;
    //        }
    //
    //        @Override
    //        public View getView(int position, View convertView, ViewGroup parent) {
    //            Suggestion suggestion = (Suggestion) getItem(position);
    //            LayoutInflater inflater = mActivity.getLayoutInflater();
    //            AggregationSuggestionView suggestionView =
    //                    (AggregationSuggestionView) inflater.inflate(
    //                            R.layout.aggregation_suggestions_item, null);
    //            suggestionView.setNewContact(mSetNewContact);
    //            suggestionView.setListener(mListener);
    //            suggestionView.bindSuggestion(suggestion);
    //            return suggestionView;
    //        }
    //
    //        @Override
    //        public long getItemId(int position) {
    //            return position;
    //        }
    //
    //        @Override
    //        public Object getItem(int position) {
    //            return mSuggestions.get(position);
    //        }
    //
    //        @Override
    //        public int getCount() {
    //            return mSuggestions.size();
    //        }
    //    }
    //
    //    private OnItemClickListener mAggregationSuggestionItemClickListener =
    //            new OnItemClickListener() {
    //        @Override
    //        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    //            final AggregationSuggestionView suggestionView = (AggregationSuggestionView) view;
    //            suggestionView.handleItemClickEvent();
    //            mAggregationSuggestionPopup.dismiss();
    //            mAggregationSuggestionPopup = null;
    //        }
    //    };
    //
    private boolean mAutoAddToDefaultGroup;

    private boolean mEnabled = true;
    private boolean mRequestFocus;
    private boolean mNewLocalProfile = false;
    private boolean mIsUserProfile = false;

    public ContactEditorFragment() {
    }

    public void setEnabled(boolean enabled) {
        if (mEnabled != enabled) {
            mEnabled = enabled;
            if (mContent != null) {
                int count = mContent.getChildCount();
                for (int i = 0; i < count; i++) {
                    mContent.getChildAt(i).setEnabled(enabled);
                }
            }
            //            setAggregationSuggestionViewEnabled(enabled);
            final SherlockFragmentActivity activity = getSherlockActivity();
            if (activity != null)
                activity.supportInvalidateOptionsMenu();
        }
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        mContext = activity;
        mEditorUtils = ContactEditorUtils.getInstance(mContext);
    }

    @Override
    public void onStop() {
        super.onStop();
        //        if (mAggregationSuggestionEngine != null) {
        //            mAggregationSuggestionEngine.quit();
        //        }

        // If anything was left unsaved, save it now but keep the editor open. TODO
        if (/*!getActivity().isChangingConfigurations() && */ mStatus == Status.EDITING) {
            save(SaveMode.RELOAD);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
        final View view = inflater.inflate(R.layout.contact_editor_fragment, container, false);

        mContent = (LinearLayout) view.findViewById(R.id.editors);

        setHasOptionsMenu(true);

        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        validateAction(mAction);

        // Handle initial actions only when existing state missing
        final boolean hasIncomingState = savedInstanceState != null;

        if (mState == null) {
            // The delta list may not have finished loading before orientation change happens.
            // In this case, there will be a saved state but deltas will be missing.  Reload from
            // database.
            if (Intent.ACTION_EDIT.equals(mAction)) {
                // Either...
                // 1) orientation change but load never finished.
                // or
                // 2) not an orientation change.  data needs to be loaded for first time.
                getLoaderManager().initLoader(LOADER_DATA, null, mDataLoaderListener);
            }
        } else {
            // Orientation change, we already have mState, it was loaded by onCreate
            bindEditors();
        }

        if (!hasIncomingState) {
            if (Intent.ACTION_INSERT.equals(mAction)) {
                selectAccountAndCreateContact();
            }
        }
    }

    /**
     * Checks if the requested action is valid.
     *
     * @param action The action to test.
     * @throws IllegalArgumentException when the action is invalid.
     */
    private void validateAction(String action) {
        if (Intent.ACTION_EDIT.equals(action) || Intent.ACTION_INSERT.equals(action)
                || ScContactEditorActivity.ACTION_SAVE_COMPLETED.equals(action)) {
            return;
        }
        throw new IllegalArgumentException(
                "Unknown Action String " + mAction + ". Only support " + Intent.ACTION_EDIT + " or "
                        + Intent.ACTION_INSERT + " or " + ScContactEditorActivity.ACTION_SAVE_COMPLETED);
    }

    @Override
    public void onStart() {
        getLoaderManager().initLoader(LOADER_GROUPS, null, mGroupLoaderListener);
        super.onStart();
    }

    public void load(String action, Uri lookupUri, Bundle intentExtras) {
        mAction = action;
        mLookupUri = lookupUri;
        mIntentExtras = intentExtras;
        mAutoAddToDefaultGroup = mIntentExtras != null
                && mIntentExtras.containsKey(INTENT_EXTRA_ADD_TO_DEFAULT_DIRECTORY);
        mNewLocalProfile = mIntentExtras != null && mIntentExtras.getBoolean(INTENT_EXTRA_NEW_LOCAL_PROFILE);
    }

    public void setListener(Listener value) {
        mListener = value;
    }

    @Override
    public void onCreate(Bundle savedState) {
        if (savedState != null) {
            // Restore mUri before calling super.onCreate so that onInitializeLoaders
            // would already have a uri and an action to work with
            mLookupUri = savedState.getParcelable(KEY_URI);
            mAction = savedState.getString(KEY_ACTION);
        }

        super.onCreate(savedState);

        if (savedState == null) {
            // If savedState is non-null, onRestoreInstanceState() will restore the generator.
            mViewIdGenerator = new ViewIdGenerator();
        } else {
            // Read state from savedState. No loading involved here
            mState = savedState.<RawContactDeltaList>getParcelable(KEY_EDIT_STATE);
            mRawContactIdRequestingPhoto = savedState.getLong(KEY_RAW_CONTACT_ID_REQUESTING_PHOTO);
            mViewIdGenerator = savedState.getParcelable(KEY_VIEW_ID_GENERATOR);
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
                mCurrentPhotoFile = savedState.getString(KEY_CURRENT_PHOTO_FILE);
            else
                mCurrentPhotoUri = savedState.getParcelable(KEY_CURRENT_PHOTO_URI);
            mContactIdForJoin = savedState.getLong(KEY_CONTACT_ID_FOR_JOIN);
            mContactWritableForJoin = savedState.getBoolean(KEY_CONTACT_WRITABLE_FOR_JOIN);
            mAggregationSuggestionsRawContactId = savedState.getLong(KEY_SHOW_JOIN_SUGGESTIONS);
            mEnabled = savedState.getBoolean(KEY_ENABLED);
            mStatus = savedState.getInt(KEY_STATUS);
            mNewLocalProfile = savedState.getBoolean(KEY_NEW_LOCAL_PROFILE);
            mIsUserProfile = savedState.getBoolean(KEY_IS_USER_PROFILE);
            mUpdatedPhotos = savedState.getParcelable(KEY_UPDATED_PHOTOS);
        }
    }

    public void setData(Contact data) {
        // If we have already loaded data, we do not want to change it here to not confuse the user
        if (mState != null) {
            Log.v(TAG, "Ignoring background change. This will have to be rebased later");
            return;
        }
        bindEditorsForExistingContact(data);
    }

    private void bindEditorsForExistingContact(Contact contact) {
        setEnabled(true);

        mState = contact.createRawContactDeltaList();
        setIntentExtras(mIntentExtras);
        mIntentExtras = null;

        mIsUserProfile = contact.isUserProfile();
        mRequestFocus = true;

        bindEditors();
    }

    /**
     * Merges extras from the intent.
     */
    public void setIntentExtras(Bundle extras) {
        if (extras == null || extras.size() == 0) {
            return;
        }

        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
        for (RawContactDelta state : mState) {
            final AccountType type = state.getAccountType(accountTypes);
            if (type.areContactsWritable()) {
                // Apply extras to the first writable raw contact only
                RawContactModifier.parseExtras(mContext, type, state, extras);
                break;
            }
        }
    }

    private void selectAccountAndCreateContact() {
        createContact();
    }

    /**
     * Create a contact by automatically selecting the first account. If there's no available
     * account, a device-local contact should be created.
     */
    private void createContact() {
        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
        final AccountType accountType = accountTypes.getAccountType();

        bindEditorsForNewContact(accountType);
    }

    /**
     * Removes a current editor ({@link #mState}) and rebinds new editor for a new account.
     * Some of old data are reused with new restriction enforced by the new account.
     *
     * @ param oldState Old data being edited.
     * @ param oldAccount Old account associated with oldState.
     * @ param newAccount New account to be used.
     */
    //    private void rebindEditorsForNewContact(RawContactDelta oldState, AccountWithDataSet oldAccount, AccountWithDataSet newAccount) {
    //        AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
    //        AccountType oldAccountType = accountTypes.getAccountType(oldAccount.type, oldAccount.dataSet);
    //        AccountType newAccountType = accountTypes.getAccountType(newAccount.type, newAccount.dataSet);
    //
    //        if (newAccountType.getCreateContactActivityClassName() != null) {
    //            Log.w(TAG, "external activity called in rebind situation");
    //            if (mListener != null) {
    //                mListener.onCustomCreateContactActivityRequested(newAccount, mIntentExtras);
    //            }
    //        } else {
    //            mState = null;
    //            bindEditorsForNewContact(newAccount, newAccountType, oldState, oldAccountType);
    //        }
    //    }

    private void bindEditorsForNewContact(final AccountType accountType) {
        bindEditorsForNewContact(accountType, null, null);
    }

    // AccountType shall be the FallbackAccountType
    private void bindEditorsForNewContact(final AccountType newAccountType, RawContactDelta oldState,
            AccountType oldAccountType) {

        mStatus = Status.EDITING;

        final RawContact rawContact = new RawContact(mContext);
        rawContact.setAccountToLocal();

        RawContactDelta insert = new RawContactDelta(RawContactDelta.ValuesDelta.fromAfter(rawContact.getValues()));
        if (oldState == null) {
            // Parse any values from incoming intent
            RawContactModifier.parseExtras(mContext, newAccountType, insert, mIntentExtras);
        } else {
            RawContactModifier.migrateStateForNewContact(mContext, oldState, insert, oldAccountType,
                    newAccountType);
        }

        // Ensure we have some default fields (if the account type does not support a field,
        // ensureKind will not add it, so it is safe to add e.g. Event)
        RawContactModifier.ensureKindExists(insert, newAccountType, Phone.CONTENT_ITEM_TYPE);
        RawContactModifier.ensureKindExists(insert, newAccountType, Email.CONTENT_ITEM_TYPE);
        RawContactModifier.ensureKindExists(insert, newAccountType, Organization.CONTENT_ITEM_TYPE);
        RawContactModifier.ensureKindExists(insert, newAccountType, SipAddress.CONTENT_ITEM_TYPE);
        RawContactModifier.ensureKindExists(insert, newAccountType, StructuredPostal.CONTENT_ITEM_TYPE);

        // Set the correct Contact tyep for saving the contact as a profile
        if (mNewLocalProfile) {
            insert.setProfileEdit();
        }

        if (mState == null) {
            // Create state if none exists yet
            mState = RawContactDeltaList.fromSingle(insert);
        } else {
            // Add contact onto end of existing state
            mState.add(insert);
        }
        mRequestFocus = true;

        bindEditors();
    }

    private void bindEditors() {
        // bindEditors() can only bind views if there is data in mState, so immediately return
        // if mState is null
        if (mState == null) {
            return;
        }

        // Sort the editors
        Collections.sort(mState, mComparator);

        // Remove any existing editors and rebuild any visible
        mContent.removeAllViews();

        final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
        int numRawContacts = mState.size();

        for (int i = 0; i < numRawContacts; i++) {
            // TODO ensure proper ordering of entities in the list
            final RawContactDelta rawContactDelta = mState.get(i);
            if (!rawContactDelta.isVisible())
                continue;

            final AccountType type = rawContactDelta.getAccountType(accountTypes);
            final long rawContactId = rawContactDelta.getRawContactId();

            final BaseRawContactEditorView editor;
            editor = (RawContactEditorView) inflater.inflate(R.layout.raw_contact_editor_view, mContent, false);
            ((RawContactEditorView) editor).setParent(this);

            editor.setEnabled(mEnabled);

            mContent.addView(editor);

            editor.setState(rawContactDelta, type, mViewIdGenerator, isEditingUserProfile());

            // Set up the photo handler.
            bindPhotoHandler(editor, type, mState);

            // If a new photo was chosen but not yet saved, we need to
            // update the thumbnail to reflect this.
            Bitmap bitmap = updatedBitmapForRawContact(rawContactId);
            if (bitmap != null)
                editor.setPhotoBitmap(bitmap);

            if (editor instanceof RawContactEditorView) {
                final Activity activity = getActivity();
                final RawContactEditorView rawContactEditor = (RawContactEditorView) editor;
                Editor.EditorListener listener = new Editor.EditorListener() {

                    @Override
                    public void onRequest(int request) {
                        if (activity.isFinishing()) { // Make sure activity is still running.
                            return;
                        }
                        if (request == Editor.EditorListener.FIELD_CHANGED && !isEditingUserProfile()) {
                            acquireAggregationSuggestions(activity, rawContactEditor);
                        }
                    }

                    @Override
                    public void onDeleteRequested(Editor removedEditor) {
                    }
                };

                final TextFieldsEditorView nameEditor = rawContactEditor.getNameEditor();
                if (mRequestFocus) {
                    nameEditor.requestFocus();
                    mRequestFocus = false;
                }
                nameEditor.setEditorListener(listener);

                final TextFieldsEditorView phoneticNameEditor = rawContactEditor.getPhoneticNameEditor();
                phoneticNameEditor.setEditorListener(listener);
                rawContactEditor.setAutoAddToDefaultGroup(mAutoAddToDefaultGroup);

                if (rawContactId == mAggregationSuggestionsRawContactId) {
                    acquireAggregationSuggestions(activity, rawContactEditor);
                }
            }
        }

        mRequestFocus = false;

        bindGroupMetaData();

        // Show editor now that we've loaded state
        mContent.setVisibility(View.VISIBLE);

        // Refresh Action Bar as the visibility of the join command
        // Activity can be null if we have been detached from the Activity
        final SherlockFragmentActivity activity = getSherlockActivity();
        if (activity != null)
            activity.supportInvalidateOptionsMenu();
    }

    /**
     * If we've stashed a temporary file containing a contact's new photo,
     * decode it and return the bitmap.
     * @param rawContactId identifies the raw-contact whose Bitmap we'll try to return.
     * @return Bitmap of photo for specified raw-contact, or null
    */
    private Bitmap updatedBitmapForRawContact(long rawContactId) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            String path = mUpdatedPhotos.getString(String.valueOf(rawContactId));
            if (path == null)
                return null;
            return BitmapFactory.decodeFile(path);
        } else {
            //        Uri uri = mUpdatedPhotos.getParcelable(String.valueOf(rawContactId));
            //        if (uri == null)
            //            return null;
            //        try {
            //            return ContactPhotoUtils19.getBitmapFromUri(mContext, uri);
            //        } catch (FileNotFoundException e) {
            //            e.printStackTrace();
            //            return null;
            //        }
            return null;
        }
    }

    private void bindPhotoHandler(BaseRawContactEditorView editor, AccountType type, RawContactDeltaList state) {
        final int mode;
        if (type.areContactsWritable()) {
            if (editor.hasSetPhoto()) {
                if (hasMoreThanOnePhoto()) {
                    mode = PhotoActionPopup.Modes.PHOTO_ALLOW_PRIMARY;
                } else {
                    mode = PhotoActionPopup.Modes.PHOTO_DISALLOW_PRIMARY;
                }
            } else {
                mode = PhotoActionPopup.Modes.NO_PHOTO;
            }
        } else {
            if (editor.hasSetPhoto() && hasMoreThanOnePhoto()) {
                mode = PhotoActionPopup.Modes.READ_ONLY_ALLOW_PRIMARY;
            } else {
                // Read-only and either no photo or the only photo ==> no options
                editor.getPhotoEditor().setEditorListener(null);
                return;
            }
        }
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            final PhotoHandler photoHandler = new PhotoHandler(mContext, editor, mode, state, this);
            editor.getPhotoEditor()
                    .setEditorListener((PhotoHandler.PhotoEditorListener) photoHandler.getListener());

            // Note a newly created raw contact gets some random negative ID, so any value is valid
            // here. (i.e. don't check against -1 or anything.)
            if (mRawContactIdRequestingPhoto == editor.getRawContactId()) {
                mCurrentPhotoHandler = photoHandler;
            }
        } else {
            final PhotoHandler19 photoHandler = new PhotoHandler19(mContext, editor, mode, state, this);
            editor.getPhotoEditor()
                    .setEditorListener((PhotoHandler19.PhotoEditorListener) photoHandler.getListener());

            // Note a newly created raw contact gets some random negative ID, so any value is valid
            // here. (i.e. don't check against -1 or anything.)
            if (mRawContactIdRequestingPhoto == editor.getRawContactId()) {
                mCurrentPhotoHandler19 = photoHandler;
            }
        }
    }

    private void bindGroupMetaData() {
        if (mGroupMetaData == null) {
            return;
        }

        int editorCount = mContent.getChildCount();
        for (int i = 0; i < editorCount; i++) {
            BaseRawContactEditorView editor = (BaseRawContactEditorView) mContent.getChildAt(i);
            editor.setGroupMetaData(mGroupMetaData);
        }
    }

    private void saveDefaultAccountIfNecessary() {
        // Verify that this is a newly created contact, that the contact is composed of only
        // 1 raw contact, and that the contact is not a user profile.
        if (!Intent.ACTION_INSERT.equals(mAction) && mState.size() == 1 && !isEditingUserProfile()) {
            return;
        }

        // Find the associated account for this contact (retrieve it here because there are
        // multiple paths to creating a contact and this ensures we always have the correct
        // account).
        final RawContactDelta rawContactDelta = mState.get(0);
        mEditorUtils.saveDefaultAndAllAccounts();
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
        inflater.inflate(R.menu.edit_contact, menu);
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        // This supports the keyboard shortcut to save changes to a contact but shouldn't be visible
        // because the custom action bar contains the "save" button now (not the overflow menu).
        // TODO: Find a better way to handle shortcuts, i.e. onKeyDown()?
        final MenuItem doneMenu = menu.findItem(R.id.menu_done);
        //        final MenuItem splitMenu = menu.findItem(R.id.menu_split);
        //        final MenuItem joinMenu = menu.findItem(R.id.menu_join);
        final MenuItem helpMenu = menu.findItem(R.id.menu_help);

        // Set visibility of menus
        doneMenu.setVisible(false);

        //        // Split only if more than one raw profile and not a user profile
        //        splitMenu.setVisible(mState != null && mState.size() > 1 && !isEditingUserProfile());
        //
        //        // Cannot join a user profile
        //        joinMenu.setVisible(!isEditingUserProfile());

        // help menu depending on whether this is inserting or editing
        if (Intent.ACTION_INSERT.equals(mAction)) {
            // inserting
            HelpUtils.prepareHelpMenuItem(mContext, helpMenu, R.string.help_url_people_add);
        } else if (Intent.ACTION_EDIT.equals(mAction)) {
            // editing
            HelpUtils.prepareHelpMenuItem(mContext, helpMenu, R.string.help_url_people_edit);
        } else {
            // something else, so don't show the help menu
            helpMenu.setVisible(false);
        }

        int size = menu.size();
        for (int i = 0; i < size; i++) {
            menu.getItem(i).setEnabled(mEnabled);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_done:
            return save(SaveMode.CLOSE);
        case R.id.menu_discard:
            return revert();
        //            case R.id.menu_split:
        //                return doSplitContactAction();
        //            case R.id.menu_join:
        //                return doJoinContactAction();
        }
        return false;
    }

    //    private boolean doSplitContactAction() {
    //        if (!hasValidState()) return false;
    //
    //        final SplitContactConfirmationDialogFragment dialog = new SplitContactConfirmationDialogFragment();
    //        dialog.setTargetFragment(this, 0);
    //        dialog.show(getFragmentManager(), SplitContactConfirmationDialogFragment.TAG);
    //        return true;
    //    }

    //    private boolean doJoinContactAction() {
    //        if (!hasValidState()) {
    //            return false;
    //        }
    //
    //        // If we just started creating a new contact and haven't added any data, it's too
    //        // early to do a join
    //        if (mState.size() == 1 && mState.get(0).isContactInsert() && !hasPendingChanges()) {
    //            Toast.makeText(mContext, R.string.toast_join_with_empty_contact, Toast.LENGTH_LONG).show();
    //            return true;
    //        }
    //
    //        return save(SaveMode.JOIN);
    //    }
    //
    /**
     * Check if our internal {@link #mState} is valid, usually checked before
     * performing user actions.
     */
    private boolean hasValidState() {
        return mState != null && mState.size() > 0;
    }

    /**
     * Return true if there are any edits to the current contact which need to
     * be saved.
     */
    private boolean hasPendingChanges() {
        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
        return RawContactModifier.hasChanges(mState, accountTypes);
    }

    /**
     * Saves or creates the contact based on the mode, and if successful
     * finishes the activity.
     */
    public boolean save(int saveMode) {
        if (!hasValidState() || mStatus != Status.EDITING) {
            return false;
        }

        // If we are about to close the editor - there is no need to refresh the data
        if (saveMode == SaveMode.CLOSE || saveMode == SaveMode.SPLIT) {
            getLoaderManager().destroyLoader(LOADER_DATA);
        }

        mStatus = Status.SAVING;

        if (!hasPendingChanges()) {
            if (mLookupUri == null && saveMode == SaveMode.RELOAD) {
                // We don't have anything to save and there isn't even an existing contact yet.
                // Nothing to do, simply go back to editing mode
                mStatus = Status.EDITING;
                return true;
            }
            onSaveCompleted(false, saveMode, mLookupUri != null, mLookupUri);
            return true;
        }

        setEnabled(false);

        // Store account as default account, only if this is a new contact
        saveDefaultAccountIfNecessary();
        checkSilentSip();

        // Save contact
        Intent intent = ScContactSaveService.createSaveContactIntent(mContext, mState, SAVE_MODE_EXTRA_KEY,
                saveMode, isEditingUserProfile(), ((Activity) mContext).getClass(),
                ScContactEditorActivity.ACTION_SAVE_COMPLETED, mUpdatedPhotos);
        mContext.startService(intent);

        // Don't try to save the same photos twice.
        mUpdatedPhotos = new Bundle();

        return true;
    }

    /**
     * Check for a SipAddress in the current state and append SC SIP domain if it does not contains a domain.
     */
    private void checkSilentSip() {
        // Check if we are displaying anything here
        for (RawContactDelta state : mState) {
            boolean hasEntries = state.hasMimeEntries(SipAddress.CONTENT_ITEM_TYPE);
            if (!hasEntries)
                continue;
            for (RawContactDelta.ValuesDelta entry : state.getMimeEntries(SipAddress.CONTENT_ITEM_TYPE)) {
                final String address = entry.getAsString(SipAddress.SIP_ADDRESS);
                if (!TextUtils.isEmpty(address) && !PhoneNumberHelper.isUriNumber(address)) {
                    entry.put(SipAddress.SIP_ADDRESS, address + getString(R.string.sc_sip_domain_0));
                }
            }
        }
    }

    public static class CancelEditDialogFragment extends DialogFragment {

        public static void show(ContactEditorFragment fragment) {
            CancelEditDialogFragment dialog = new CancelEditDialogFragment();
            dialog.setTargetFragment(fragment, 0);
            dialog.show(fragment.getFragmentManager(), "cancelEditor");
        }

        @TargetApi(Build.VERSION_CODES.HONEYCOMB)
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
                builder.setIconAttribute(android.R.attr.alertDialogIcon);

            AlertDialog dialog = builder.setMessage(R.string.cancel_confirmation_dialog_message)
                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int whichButton) {
                            ((ContactEditorFragment) getTargetFragment()).doRevertAction();
                        }
                    }).setNegativeButton(android.R.string.cancel, null).create();
            return dialog;
        }
    }

    private boolean revert() {
        if (mState == null || !hasPendingChanges()) {
            doRevertAction();
        } else {
            CancelEditDialogFragment.show(this);
        }
        return true;
    }

    private void doRevertAction() {
        // When this Fragment is closed we don't want it to auto-save
        mStatus = Status.CLOSING;
        if (mListener != null)
            mListener.onReverted();
    }

    public void doSaveAction() {
        save(SaveMode.CLOSE);
    }

    public void onJoinCompleted(Uri uri) {
        onSaveCompleted(false, SaveMode.RELOAD, uri != null, uri);
    }

    public void onSaveCompleted(boolean hadChanges, int saveMode, boolean saveSucceeded, Uri contactLookupUri) {
        if (hadChanges) {
            if (saveSucceeded) {
                if (saveMode != SaveMode.JOIN) {
                    Toast.makeText(mContext, R.string.contactSavedToast, Toast.LENGTH_SHORT).show();
                }
            } else {
                Toast.makeText(mContext, R.string.contactSavedErrorToast, Toast.LENGTH_LONG).show();
            }
        }
        switch (saveMode) {
        case SaveMode.CLOSE:
        case SaveMode.HOME:
            final Intent resultIntent;
            if (saveSucceeded && contactLookupUri != null) {
                final String requestAuthority = mLookupUri == null ? null : mLookupUri.getAuthority();

                resultIntent = new Intent();
                resultIntent.setAction(Intent.ACTION_VIEW);

                // Otherwise pass back a lookup-style Uri
                resultIntent.setData(contactLookupUri);
            } else {
                resultIntent = null;
            }
            // It is already saved, so prevent that it is saved again
            mStatus = Status.CLOSING;
            if (mListener != null)
                mListener.onSaveFinished(resultIntent);
            break;

        case SaveMode.RELOAD:
        case SaveMode.JOIN:
            if (saveSucceeded && contactLookupUri != null) {
                //                    // If it was a JOIN, we are now ready to bring up the join activity.
                //                    if (saveMode == SaveMode.JOIN && hasValidState()) {
                //                        showJoinAggregateActivity(contactLookupUri);
                //                    }

                // If this was in INSERT, we are changing into an EDIT now.
                // If it already was an EDIT, we are changing to the new Uri now
                mState = null;
                load(Intent.ACTION_EDIT, contactLookupUri, null);
                mStatus = Status.LOADING;
                getLoaderManager().restartLoader(LOADER_DATA, null, mDataLoaderListener);
            }
            break;

        case SaveMode.SPLIT:
            mStatus = Status.CLOSING;
            if (mListener != null) {
                mListener.onContactSplit(contactLookupUri);
            } else {
                Log.d(TAG, "No listener registered, can not call onSplitFinished");
            }
            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)
     */
    //    private void showJoinAggregateActivity(Uri contactLookupUri) {
    //        if (contactLookupUri == null || !isAdded()) {
    //            return;
    //        }
    //
    //        mContactIdForJoin = ContentUris.parseId(contactLookupUri);
    //        mContactWritableForJoin = isContactWritable();
    //        final Intent intent = new Intent(JoinContactActivity.JOIN_CONTACT);
    //        intent.putExtra(JoinContactActivity.EXTRA_TARGET_CONTACT_ID, mContactIdForJoin);
    //        startActivityForResult(intent, REQUEST_CODE_JOIN);
    //    }

    /**
     * Performs aggregation with the contact selected by the user from suggestions or A-Z list.
     */
    //    private void joinAggregate(final long contactId) {
    //        Intent intent = ContactSaveService.createJoinContactsIntent(mContext, mContactIdForJoin,
    //                contactId, mContactWritableForJoin,
    //                ContactEditorActivity.class, ContactEditorActivity.ACTION_JOIN_COMPLETED);
    //        mContext.startService(intent);
    //    }
    //
    /**
     * Returns true if there is at least one writable raw contact in the current contact.
     */
    //    private boolean isContactWritable() {
    //        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
    //        int size = mState.size();
    //        for (int i = 0; i < size; i++) {
    //            RawContactDelta entity = mState.get(i);
    //            final AccountType type = entity.getAccountType(accountTypes);
    //            if (type.areContactsWritable()) {
    //                return true;
    //            }
    //        }
    //        return false;
    //    }

    private boolean isEditingUserProfile() {
        return mNewLocalProfile || mIsUserProfile;
    }

    public static interface Listener {
        /**
         * Contact was not found, so somehow close this fragment. This is raised after a contact
         * is removed via Menu/Delete (unless it was a new contact)
         */
        void onContactNotFound();

        /**
         * Contact was split, so we can close now.
         * @param newLookupUri The lookup uri of the new contact that should be shown to the user.
         * The editor tries best to chose the most natural contact here.
         */
        void onContactSplit(Uri newLookupUri);

        /**
         * User has tapped Revert, close the fragment now.
         */
        void onReverted();

        /**
         * Contact was saved and the Fragment can now be closed safely.
         */
        void onSaveFinished(Intent resultIntent);

        //        /**
        //         * User switched to editing a different contact (a suggestion from the
        //         * aggregation engine).
        //         */
        //        void onEditOtherContactRequested(Uri contactLookupUri, ArrayList<ContentValues> contentValues);
        //
        //        /**
        //         * Contact is being created for an external account that provides its own
        //         * new contact activity.
        //         */
        //        void onCustomCreateContactActivityRequested(AccountWithDataSet account,  Bundle intentExtras);
        //
        //        /**
        //         * The edited raw contact belongs to an external account that provides
        //         * its own edit activity.
        //         *
        //         * @param redirect indicates that the current editor should be closed
        //         *            before the custom editor is shown.
        //         */
        //        void onCustomEditContactActivityRequested(AccountWithDataSet account, Uri rawContactUri, Bundle intentExtras, boolean redirect);
    }

    private class EntityDeltaComparator implements Comparator<RawContactDelta> {
        /**
         * Compare EntityDeltas for sorting the stack of editors.
         */
        @Override
        public int compare(RawContactDelta one, RawContactDelta two) {
            // Check direct equality
            if (one.equals(two)) {
                return 0;
            }

            // Both are in the same account, fall back to contact ID
            Long oneId = one.getRawContactId();
            Long twoId = two.getRawContactId();
            if (oneId == null) {
                return -1;
            } else if (twoId == null) {
                return 1;
            }

            return (int) (oneId - twoId);
        }
    }

    /**
     * Returns the contact ID for the currently edited contact or 0 if the contact is new.
     */
    protected long getContactId() {
        if (mState != null) {
            for (RawContactDelta rawContact : mState) {
                Long contactId = rawContact.getValues().getAsLong(RawContacts._ID);
                if (contactId != null) {
                    return contactId;
                }
            }
        }
        return 0;
    }

    /**
     * Triggers an asynchronous search for aggregation suggestions.
     */
    private void acquireAggregationSuggestions(Context context, RawContactEditorView rawContactEditor) {
    }

    /**
     * Joins the suggested contact (specified by the id's of constituent raw
     * contacts), save all changes, and stay in the editor.
     */
    protected void doJoinSuggestedContact(long[] rawContactIds) {
        if (!hasValidState() || mStatus != Status.EDITING) {
            return;
        }

        mState.setJoinWithRawContacts(rawContactIds);
        save(SaveMode.RELOAD);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putParcelable(KEY_URI, mLookupUri);
        outState.putString(KEY_ACTION, mAction);

        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 (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
            outState.putString(KEY_CURRENT_PHOTO_FILE, mCurrentPhotoFile);
        else
            outState.putParcelable(KEY_CURRENT_PHOTO_URI, mCurrentPhotoUri);
        outState.putLong(KEY_CONTACT_ID_FOR_JOIN, mContactIdForJoin);
        outState.putBoolean(KEY_CONTACT_WRITABLE_FOR_JOIN, mContactWritableForJoin);
        outState.putLong(KEY_SHOW_JOIN_SUGGESTIONS, mAggregationSuggestionsRawContactId);
        outState.putBoolean(KEY_ENABLED, mEnabled);
        outState.putBoolean(KEY_NEW_LOCAL_PROFILE, mNewLocalProfile);
        outState.putBoolean(KEY_IS_USER_PROFILE, mIsUserProfile);
        outState.putInt(KEY_STATUS, mStatus);
        outState.putParcelable(KEY_UPDATED_PHOTOS, mUpdatedPhotos);

        super.onSaveInstanceState(outState);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (mStatus == Status.SUB_ACTIVITY) {
            mStatus = Status.EDITING;
        }

        // See if the photo selection handler handles this result.
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            if (mCurrentPhotoHandler != null
                    && mCurrentPhotoHandler.handlePhotoActivityResult(requestCode, resultCode, data)) {
                return;
            }
        } else {
            if (mCurrentPhotoHandler19 != null
                    && mCurrentPhotoHandler19.handlePhotoActivityResult(requestCode, resultCode, data)) {
                return;
            }
        }
    }

    /**
     * Sets the photo stored in mPhoto and writes it to the RawContact with the given id
     */
    private void setPhoto(long rawContact, Bitmap photo, String photoFile, Uri photoUri) {
        BaseRawContactEditorView requestingEditor = getRawContactEditorView(rawContact);

        if (photo == null || photo.getHeight() < 0 || photo.getWidth() < 0) {
            // This is unexpected.
            Log.w(TAG, "Invalid bitmap passed to setPhoto()");
        }

        if (requestingEditor != null) {
            requestingEditor.setPhotoBitmap(photo);
        } else {
            Log.w(TAG, "The contact that requested the photo is no longer present.");
        }
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            final String croppedPhotoPath = ContactPhotoUtils.pathForCroppedPhoto(mContext, photoFile);
            mUpdatedPhotos.putString(String.valueOf(rawContact), croppedPhotoPath);
        } else
            mUpdatedPhotos.putParcelable(String.valueOf(rawContact), photoUri);
    }

    /**
     * Finds raw contact editor view for the given rawContactId.
     */
    public BaseRawContactEditorView getRawContactEditorView(long rawContactId) {
        for (int i = 0; i < mContent.getChildCount(); i++) {
            final View childView = mContent.getChildAt(i);
            if (childView instanceof BaseRawContactEditorView) {
                final BaseRawContactEditorView editor = (BaseRawContactEditorView) childView;
                if (editor.getRawContactId() == rawContactId) {
                    return editor;
                }
            }
        }
        return null;
    }

    /**
     * Returns true if there is currently more than one photo on screen.
     */
    private boolean hasMoreThanOnePhoto() {
        int countWithPicture = 0;
        final int numEntities = mState.size();
        for (int i = 0; i < numEntities; i++) {
            final RawContactDelta entity = mState.get(i);
            if (entity.isVisible()) {
                final RawContactDelta.ValuesDelta primary = entity.getPrimaryEntry(Photo.CONTENT_ITEM_TYPE);
                if (primary != null && primary.getPhoto() != null) {
                    countWithPicture++;
                } else {
                    final long rawContactId = entity.getRawContactId();
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
                        final String path = mUpdatedPhotos.getString(String.valueOf(rawContactId));
                        if (path != null) {
                            final File file = new File(path);
                            if (file.exists()) {
                                countWithPicture++;
                            }
                        }
                    } else {
                        final Uri uri = mUpdatedPhotos.getParcelable(String.valueOf(rawContactId));
                        if (uri != null) {
                            try {
                                mContext.getContentResolver().openInputStream(uri);
                                countWithPicture++;
                            } catch (FileNotFoundException e) {
                            }
                        }
                    }
                }

                if (countWithPicture > 1) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * The listener for the data loader
     */
    private final LoaderManager.LoaderCallbacks<Contact> mDataLoaderListener = new LoaderManager.LoaderCallbacks<Contact>() {
        @Override
        public Loader<Contact> onCreateLoader(int id, Bundle args) {
            mLoaderStartTime = SystemClock.elapsedRealtime();
            return new ContactLoader(mContext, mLookupUri, true);
        }

        @Override
        public void onLoadFinished(Loader<Contact> loader, Contact data) {
            final long loaderCurrentTime = SystemClock.elapsedRealtime();
            Log.v(TAG, "Time needed for loading: " + (loaderCurrentTime - mLoaderStartTime));
            if (!data.isLoaded()) {
                // Item has been deleted
                Log.i(TAG, "No contact found. Closing activity");
                if (mListener != null)
                    mListener.onContactNotFound();
                return;
            }

            mStatus = Status.EDITING;
            mLookupUri = data.getLookupUri();
            final long setDataStartTime = SystemClock.elapsedRealtime();
            setData(data);
            final long setDataEndTime = SystemClock.elapsedRealtime();

            Log.v(TAG, "Time needed for setting UI: " + (setDataEndTime - setDataStartTime));
        }

        @Override
        public void onLoaderReset(Loader<Contact> loader) {
        }
    };

    /**
     * The listener for the group meta data loader for all groups.
     */
    private final LoaderManager.LoaderCallbacks<Cursor> mGroupLoaderListener = new LoaderManager.LoaderCallbacks<Cursor>() {

        @Override
        public CursorLoader onCreateLoader(int id, Bundle args) {
            return new GroupMetaDataLoader(mContext, Groups.CONTENT_URI);
        }

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
            mGroupMetaData = data;
            bindGroupMetaData();
        }

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
        }
    };

    //    @Override
    //    public void onSplitContactConfirmed() {
    //        if (mState == null) {
    //            // This may happen when this Fragment is recreated by the system during users
    //            // confirming the split action (and thus this method is called just before onCreate()),
    //            // for example.
    //            Log.e(TAG, "mState became null during the user's confirming split action. " +
    //                    "Cannot perform the save action.");
    //            return;
    //        }
    //
    //        mState.markRawContactsForSplitting();
    //        save(SaveMode.SPLIT);
    //    }
    //
    /**
     * Custom photo handler for the editor.  The inner listener that this creates also has a
     * reference to the editor and acts as an {@link com.silentcircle.contacts.editor.Editor.EditorListener},
     * and uses that editor to hold state information in several of the listener methods.
     */
    private final class PhotoHandler extends PhotoSelectionHandler {

        final long mRawContactId;
        private final BaseRawContactEditorView mEditor;
        private final PhotoActionListener mPhotoEditorListener;

        public PhotoHandler(Context context, BaseRawContactEditorView editor, int photoMode,
                RawContactDeltaList state, SherlockFragment fragment) {

            super(context, editor.getPhotoEditor(), photoMode, false, state, fragment);
            mEditor = editor;
            mRawContactId = editor.getRawContactId();
            mPhotoEditorListener = new PhotoEditorListener();
        }

        @Override
        public PhotoActionListener getListener() {
            return mPhotoEditorListener;
        }

        @Override
        public void startPhotoActivity(Intent intent, int requestCode, String photoFile) {
            mRawContactIdRequestingPhoto = mEditor.getRawContactId();
            mCurrentPhotoHandler = this;
            mStatus = Status.SUB_ACTIVITY;
            mCurrentPhotoFile = photoFile;
            ContactEditorFragment.this.startActivityForResult(intent, requestCode);
        }

        private final class PhotoEditorListener extends PhotoSelectionHandler.PhotoActionListener
                implements Editor.EditorListener {

            @Override
            public void onRequest(int request) {
                if (!hasValidState())
                    return;

                if (request == Editor.EditorListener.REQUEST_PICK_PHOTO) {
                    onClick(mEditor.getPhotoEditor());
                }
            }

            @Override
            public void onDeleteRequested(Editor removedEditor) {
                // The picture cannot be deleted, it can only be removed, which is handled by
                // onRemovePictureChosen()
            }

            /**
             * User has chosen to set the selected photo as the (super) primary photo
             */
            @Override
            public void onUseAsPrimaryChosen() {
                // Set the IsSuperPrimary for each editor
                int count = mContent.getChildCount();
                for (int i = 0; i < count; i++) {
                    final View childView = mContent.getChildAt(i);
                    if (childView instanceof BaseRawContactEditorView) {
                        final BaseRawContactEditorView editor = (BaseRawContactEditorView) childView;
                        final PhotoEditorView photoEditor = editor.getPhotoEditor();
                        photoEditor.setSuperPrimary(editor == mEditor);
                    }
                }
                bindEditors();
            }

            /**
             * User has chosen to remove a picture
             */
            @Override
            public void onRemovePictureChosen() {
                mEditor.setPhotoBitmap(null);

                // Prevent bitmap from being restored if rotate the device.
                // (only if we first chose a new photo before removing it)
                mUpdatedPhotos.remove(String.valueOf(mRawContactId));
                bindEditors();
            }

            @Override
            public void onPhotoSelected(Bitmap bitmap) {
                setPhoto(mRawContactId, bitmap, mCurrentPhotoFile, null);
                mCurrentPhotoHandler = null;
                bindEditors();
            }

            @Override
            public String getCurrentPhotoFile() {
                return mCurrentPhotoFile;
            }

            @Override
            public void onPhotoSelectionDismissed() {
                // Nothing to do.
            }
        }
    }

    /**
    * Custom photo handler for the editor.  The inner listener that this creates also has a
    * reference to the editor and acts as an {@link com.silentcircle.contacts.editor.Editor.EditorListener},
    * and uses that editor to hold state information in several of the listener methods.
    */
    private final class PhotoHandler19 extends PhotoSelectionHandler19 {

        final long mRawContactId;
        private final BaseRawContactEditorView mEditor;
        private final PhotoActionListener mPhotoEditorListener;

        public PhotoHandler19(Context context, BaseRawContactEditorView editor, int photoMode,
                RawContactDeltaList state, SherlockFragment fragment) {

            super(context, editor.getPhotoEditor(), photoMode, false, state, fragment);
            mEditor = editor;
            mRawContactId = editor.getRawContactId();
            mPhotoEditorListener = new PhotoEditorListener();
        }

        @Override
        public PhotoActionListener getListener() {
            return mPhotoEditorListener;
        }

        @Override
        public void startPhotoActivity(Intent intent, int requestCode, Uri photoUri) {
            mRawContactIdRequestingPhoto = mEditor.getRawContactId();
            mCurrentPhotoHandler19 = this;
            mStatus = Status.SUB_ACTIVITY;
            mCurrentPhotoUri = photoUri;
            ContactEditorFragment.this.startActivityForResult(intent, requestCode);
        }

        private final class PhotoEditorListener extends PhotoSelectionHandler19.PhotoActionListener
                implements Editor.EditorListener {

            @Override
            public void onRequest(int request) {
                if (!hasValidState())
                    return;

                if (request == Editor.EditorListener.REQUEST_PICK_PHOTO) {
                    onClick(mEditor.getPhotoEditor());
                }
            }

            @Override
            public void onDeleteRequested(Editor removedEditor) {
                // The picture cannot be deleted, it can only be removed, which is handled by
                // onRemovePictureChosen()
            }

            /**
             * User has chosen to set the selected photo as the (super) primary photo
             */
            @Override
            public void onUseAsPrimaryChosen() {
                // Set the IsSuperPrimary for each editor
                int count = mContent.getChildCount();
                for (int i = 0; i < count; i++) {
                    final View childView = mContent.getChildAt(i);
                    if (childView instanceof BaseRawContactEditorView) {
                        final BaseRawContactEditorView editor = (BaseRawContactEditorView) childView;
                        final PhotoEditorView photoEditor = editor.getPhotoEditor();
                        photoEditor.setSuperPrimary(editor == mEditor);
                    }
                }
                bindEditors();
            }

            /**
             * User has chosen to remove a picture
             */
            @Override
            public void onRemovePictureChosen() {
                mEditor.setPhotoBitmap(null);

                // Prevent bitmap from being restored if rotate the device.
                // (only if we first chose a new photo before removing it)
                mUpdatedPhotos.remove(String.valueOf(mRawContactId));
                bindEditors();
            }

            @Override
            public void onPhotoSelected(Uri uri) throws FileNotFoundException {
                final Bitmap bitmap = ContactPhotoUtils19.getBitmapFromUri(mContext, uri);
                setPhoto(mRawContactId, bitmap, null, uri);
                mCurrentPhotoHandler19 = null;
                bindEditors();
            }

            @Override
            public Uri getCurrentPhotoUri() {
                return mCurrentPhotoUri;
            }

            @Override
            public void onPhotoSelectionDismissed() {
                // Nothing to do.
            }
        }
    }
}