ContactEditorFragment.java revision 151f3e6883e5785019f7b5083dc8baf3e305dc18
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.contacts.editor;
18
19import android.app.Activity;
20import android.content.Context;
21import android.content.Intent;
22import android.graphics.Bitmap;
23import android.net.Uri;
24import android.os.Bundle;
25import android.provider.ContactsContract;
26import android.provider.ContactsContract.CommonDataKinds.StructuredName;
27import android.provider.ContactsContract.CommonDataKinds.Photo;
28import android.text.TextUtils;
29import android.util.Log;
30import android.view.LayoutInflater;
31import android.view.View;
32import android.view.ViewGroup;
33import android.widget.AdapterView;
34import android.widget.LinearLayout;
35import android.widget.ListPopupWindow;
36
37import com.android.contacts.ContactSaveService;
38import com.android.contacts.R;
39import com.android.contacts.activities.ContactEditorActivity;
40import com.android.contacts.common.model.AccountTypeManager;
41import com.android.contacts.common.model.RawContactDelta;
42import com.android.contacts.common.model.RawContactDeltaList;
43import com.android.contacts.common.model.ValuesDelta;
44import com.android.contacts.common.model.account.AccountType;
45import com.android.contacts.common.model.account.AccountWithDataSet;
46import com.android.contacts.common.util.AccountsListAdapter;
47import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter;
48import com.android.contacts.detail.PhotoSelectionHandler;
49import com.android.contacts.editor.Editor.EditorListener;
50import com.android.contacts.util.ContactPhotoUtils;
51import com.android.contacts.util.UiClosables;
52
53import java.io.FileNotFoundException;
54import java.util.Collections;
55import java.util.HashMap;
56import java.util.List;
57
58/**
59 * Contact editor with all fields displayed.
60 */
61public class ContactEditorFragment extends ContactEditorBaseFragment implements
62        RawContactReadOnlyEditorView.Listener {
63
64    private static final String KEY_EXPANDED_EDITORS = "expandedEditors";
65
66    private static final String KEY_RAW_CONTACT_ID_REQUESTING_PHOTO = "photorequester";
67    private static final String KEY_CURRENT_PHOTO_URI = "currentphotouri";
68    private static final String KEY_UPDATED_PHOTOS = "updatedPhotos";
69
70    // Used to store which raw contact editors have been expanded. Keyed on raw contact ids.
71    private HashMap<Long, Boolean> mExpandedEditors = new HashMap<Long, Boolean>();
72
73    /**
74     * The raw contact for which we started "take photo" or "choose photo from gallery" most
75     * recently.  Used to restore {@link #mCurrentPhotoHandler} after orientation change.
76     */
77    private long mRawContactIdRequestingPhoto;
78
79    /**
80     * The {@link PhotoHandler} for the photo editor for the {@link #mRawContactIdRequestingPhoto}
81     * raw contact.
82     *
83     * A {@link PhotoHandler} is created for each photo editor in {@link #bindPhotoHandler}, but
84     * the only "active" one should get the activity result.  This member represents the active
85     * one.
86     */
87    private PhotoHandler mCurrentPhotoHandler;
88    private Uri mCurrentPhotoUri;
89    private Bundle mUpdatedPhotos = new Bundle();
90
91    public ContactEditorFragment() {
92    }
93
94    @Override
95    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
96        final View view = inflater.inflate(R.layout.contact_editor_fragment, container, false);
97
98        mContent = (LinearLayout) view.findViewById(R.id.editors);
99
100        setHasOptionsMenu(true);
101
102        return view;
103    }
104
105    @Override
106    public void onCreate(Bundle savedState) {
107        super.onCreate(savedState);
108
109        if (savedState != null) {
110            mExpandedEditors = (HashMap<Long, Boolean>)
111                    savedState.getSerializable(KEY_EXPANDED_EDITORS);
112            mRawContactIdRequestingPhoto = savedState.getLong(
113                    KEY_RAW_CONTACT_ID_REQUESTING_PHOTO);
114            mCurrentPhotoUri = savedState.getParcelable(KEY_CURRENT_PHOTO_URI);
115            mUpdatedPhotos = savedState.getParcelable(KEY_UPDATED_PHOTOS);
116        }
117    }
118
119    @Override
120    public void onExternalEditorRequest(AccountWithDataSet account, Uri uri) {
121        mListener.onCustomEditContactActivityRequested(account, uri, null, false);
122    }
123
124    @Override
125    public void onEditorExpansionChanged() {
126        updatedExpandedEditorsMap();
127    }
128
129    /**
130     * Removes a current editor ({@link #mState}) and rebinds new editor for a new account.
131     * Some of old data are reused with new restriction enforced by the new account.
132     *
133     * @param oldState Old data being edited.
134     * @param oldAccount Old account associated with oldState.
135     * @param newAccount New account to be used.
136     */
137    private void rebindEditorsForNewContact(
138            RawContactDelta oldState, AccountWithDataSet oldAccount,
139            AccountWithDataSet newAccount) {
140        AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
141        AccountType oldAccountType = accountTypes.getAccountTypeForAccount(oldAccount);
142        AccountType newAccountType = accountTypes.getAccountTypeForAccount(newAccount);
143
144        if (newAccountType.getCreateContactActivityClassName() != null) {
145            Log.w(TAG, "external activity called in rebind situation");
146            if (mListener != null) {
147                mListener.onCustomCreateContactActivityRequested(newAccount, mIntentExtras);
148            }
149        } else {
150            mExistingContactDataReady = false;
151            mNewContactDataReady = false;
152            mState = new RawContactDeltaList();
153            setStateForNewContact(newAccount, newAccountType, oldState, oldAccountType);
154            if (mIsEdit) {
155                setStateForExistingContact(mDefaultDisplayName, mIsUserProfile, mRawContacts);
156            }
157        }
158    }
159
160    @Override
161    protected void setGroupMetaData() {
162        if (mGroupMetaData == null) {
163            return;
164        }
165        int editorCount = mContent.getChildCount();
166        for (int i = 0; i < editorCount; i++) {
167            BaseRawContactEditorView editor = (BaseRawContactEditorView) mContent.getChildAt(i);
168            editor.setGroupMetaData(mGroupMetaData);
169        }
170    }
171
172    @Override
173    protected void bindEditors() {
174        // bindEditors() can only bind views if there is data in mState, so immediately return
175        // if mState is null
176        if (mState.isEmpty()) {
177            return;
178        }
179
180        // Check if delta list is ready.  Delta list is populated from existing data and when
181        // editing an read-only contact, it's also populated with newly created data for the
182        // blank form.  When the data is not ready, skip. This method will be called multiple times.
183        if ((mIsEdit && !mExistingContactDataReady) || (mHasNewContact && !mNewContactDataReady)) {
184            return;
185        }
186
187        // Sort the editors
188        Collections.sort(mState, mComparator);
189
190        // Remove any existing editors and rebuild any visible
191        mContent.removeAllViews();
192
193        final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
194                Context.LAYOUT_INFLATER_SERVICE);
195        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
196        int numRawContacts = mState.size();
197
198        for (int i = 0; i < numRawContacts; i++) {
199            // TODO ensure proper ordering of entities in the list
200            final RawContactDelta rawContactDelta = mState.get(i);
201            if (!rawContactDelta.isVisible()) continue;
202
203            final AccountType type = rawContactDelta.getAccountType(accountTypes);
204            final long rawContactId = rawContactDelta.getRawContactId();
205
206            final BaseRawContactEditorView editor;
207            if (!type.areContactsWritable()) {
208                editor = (BaseRawContactEditorView) inflater.inflate(
209                        R.layout.raw_contact_readonly_editor_view, mContent, false);
210            } else {
211                editor = (RawContactEditorView) inflater.inflate(R.layout.raw_contact_editor_view,
212                        mContent, false);
213            }
214            editor.setListener(this);
215            final List<AccountWithDataSet> accounts = AccountTypeManager.getInstance(mContext)
216                    .getAccounts(true);
217            if (mHasNewContact && !mNewLocalProfile && accounts.size() > 1) {
218                addAccountSwitcher(mState.get(0), editor);
219            }
220
221            editor.setEnabled(isEnabled());
222
223            if (mExpandedEditors.containsKey(rawContactId)) {
224                editor.setCollapsed(mExpandedEditors.get(rawContactId));
225            } else {
226                // By default, only the first editor will be expanded.
227                editor.setCollapsed(i != 0);
228            }
229
230            mContent.addView(editor);
231
232            editor.setState(rawContactDelta, type, mViewIdGenerator, isEditingUserProfile());
233            editor.setCollapsible(numRawContacts > 1);
234
235            // Set up the photo handler.
236            bindPhotoHandler(editor, type, mState);
237
238            // If a new photo was chosen but not yet saved, we need to update the UI to
239            // reflect this.
240            final Uri photoUri = updatedPhotoUriForRawContact(rawContactId);
241            if (photoUri != null) editor.setFullSizedPhoto(photoUri);
242
243            if (editor instanceof RawContactEditorView) {
244                final Activity activity = getActivity();
245                final RawContactEditorView rawContactEditor = (RawContactEditorView) editor;
246                final ValuesDelta nameValuesDelta = rawContactEditor.getNameEditor().getValues();
247                final EditorListener structuredNameListener = new EditorListener() {
248
249                    @Override
250                    public void onRequest(int request) {
251                        // Make sure the activity is running
252                        if (activity.isFinishing()) {
253                            return;
254                        }
255                        if (request == EditorListener.EDITOR_FOCUS_CHANGED) {
256                            adjustNameFieldsHintDarkness(rawContactEditor);
257                            return;
258                        }
259                        if (!isEditingUserProfile()) {
260                            if (request == EditorListener.FIELD_CHANGED) {
261                                if (!nameValuesDelta.isSuperPrimary()) {
262                                    unsetSuperPrimaryForAllNameEditors();
263                                    nameValuesDelta.setSuperPrimary(true);
264                                }
265                                acquireAggregationSuggestions(activity,
266                                        rawContactEditor.getNameEditor().getRawContactId(),
267                                        rawContactEditor.getNameEditor().getValues());
268                            } else if (request == EditorListener.FIELD_TURNED_EMPTY) {
269                                if (nameValuesDelta.isSuperPrimary()) {
270                                    nameValuesDelta.setSuperPrimary(false);
271                                }
272                            }
273                        }
274                    }
275
276                    @Override
277                    public void onDeleteRequested(Editor removedEditor) {
278                    }
279                };
280                final EditorListener otherNamesListener = new EditorListener() {
281
282                    @Override
283                    public void onRequest(int request) {
284                        // Make sure the activity is running
285                        if (activity.isFinishing()) {
286                            return;
287                        }
288                        if (request == EditorListener.EDITOR_FOCUS_CHANGED) {
289                            adjustNameFieldsHintDarkness(rawContactEditor);
290                        }
291                    }
292
293                    @Override
294                    public void onDeleteRequested(Editor removedEditor) {
295                    }
296                };
297
298                final StructuredNameEditorView nameEditor = rawContactEditor.getNameEditor();
299                if (mRequestFocus) {
300                    nameEditor.requestFocus();
301                    mRequestFocus = false;
302                }
303                nameEditor.setEditorListener(structuredNameListener);
304                if (!TextUtils.isEmpty(mDefaultDisplayName)) {
305                    nameEditor.setDisplayName(mDefaultDisplayName);
306                }
307
308                final TextFieldsEditorView phoneticNameEditor =
309                        rawContactEditor.getPhoneticNameEditor();
310                phoneticNameEditor.setEditorListener(otherNamesListener);
311                rawContactEditor.setAutoAddToDefaultGroup(mAutoAddToDefaultGroup);
312
313                final TextFieldsEditorView nickNameEditor =
314                        rawContactEditor.getNickNameEditor();
315                nickNameEditor.setEditorListener(otherNamesListener);
316
317                if (isAggregationSuggestionRawContactId(rawContactId)) {
318                    acquireAggregationSuggestions(activity,
319                            rawContactEditor.getNameEditor().getRawContactId(),
320                            rawContactEditor.getNameEditor().getValues());
321                }
322
323                adjustNameFieldsHintDarkness(rawContactEditor);
324            }
325        }
326
327        mRequestFocus = false;
328
329        setGroupMetaData();
330
331        // Show editor now that we've loaded state
332        mContent.setVisibility(View.VISIBLE);
333
334        // Refresh Action Bar as the visibility of the join command
335        // Activity can be null if we have been detached from the Activity
336        invalidateOptionsMenu();
337
338        updatedExpandedEditorsMap();
339    }
340
341    private void unsetSuperPrimaryForAllNameEditors() {
342        for (int i = 0; i < mContent.getChildCount(); i++) {
343            final View view = mContent.getChildAt(i);
344            if (view instanceof RawContactEditorView) {
345                final RawContactEditorView rawContactEditorView = (RawContactEditorView) view;
346                final StructuredNameEditorView nameEditorView =
347                        rawContactEditorView.getNameEditor();
348                if (nameEditorView != null) {
349                    final ValuesDelta valuesDelta = nameEditorView.getValues();
350                    if (valuesDelta != null) {
351                        valuesDelta.setSuperPrimary(false);
352                    }
353                }
354            }
355        }
356    }
357
358    /**
359     * Adjust how dark the hint text should be on all the names' text fields.
360     *
361     * @param rawContactEditor editor to update
362     */
363    private void adjustNameFieldsHintDarkness(RawContactEditorView rawContactEditor) {
364        // Check whether fields contain focus by calling findFocus() instead of hasFocus().
365        // The hasFocus() value is not necessarily up to date.
366        final boolean nameFieldsAreNotFocused
367                = rawContactEditor.getNameEditor().findFocus() == null
368                && rawContactEditor.getPhoneticNameEditor().findFocus() == null
369                && rawContactEditor.getNickNameEditor().findFocus() == null;
370        rawContactEditor.getNameEditor().setHintColorDark(!nameFieldsAreNotFocused);
371        rawContactEditor.getPhoneticNameEditor().setHintColorDark(!nameFieldsAreNotFocused);
372        rawContactEditor.getNickNameEditor().setHintColorDark(!nameFieldsAreNotFocused);
373    }
374
375    /**
376     * Update the values in {@link #mExpandedEditors}.
377     */
378    private void updatedExpandedEditorsMap() {
379        for (int i = 0; i < mContent.getChildCount(); i++) {
380            final View childView = mContent.getChildAt(i);
381            if (childView instanceof BaseRawContactEditorView) {
382                BaseRawContactEditorView childEditor = (BaseRawContactEditorView) childView;
383                mExpandedEditors.put(childEditor.getRawContactId(), childEditor.isCollapsed());
384            }
385        }
386    }
387
388    /**
389     * If we've stashed a temporary file containing a contact's new photo, return its URI.
390     * @param rawContactId identifies the raw-contact whose Bitmap we'll try to return.
391     * @return Uru of photo for specified raw-contact, or null
392     */
393    private Uri updatedPhotoUriForRawContact(long rawContactId) {
394        return (Uri) mUpdatedPhotos.get(String.valueOf(rawContactId));
395    }
396
397    private void bindPhotoHandler(BaseRawContactEditorView editor, AccountType type,
398            RawContactDeltaList state) {
399        final int mode;
400        final boolean showIsPrimaryOption;
401        if (type.areContactsWritable()) {
402            if (editor.hasSetPhoto()) {
403                mode = PhotoActionPopup.Modes.WRITE_ABLE_PHOTO;
404                showIsPrimaryOption = hasMoreThanOnePhoto();
405            } else {
406                mode = PhotoActionPopup.Modes.NO_PHOTO;
407                showIsPrimaryOption = false;
408            }
409        } else if (editor.hasSetPhoto() && hasMoreThanOnePhoto()) {
410            mode = PhotoActionPopup.Modes.READ_ONLY_PHOTO;
411            showIsPrimaryOption = true;
412        } else {
413            // Read-only and either no photo or the only photo ==> no options
414            editor.getPhotoEditor().setEditorListener(null);
415            editor.getPhotoEditor().setShowPrimary(false);
416            return;
417        }
418        final PhotoHandler photoHandler = new PhotoHandler(mContext, editor, mode, state);
419        editor.getPhotoEditor().setEditorListener(
420                (PhotoHandler.PhotoEditorListener) photoHandler.getListener());
421        editor.getPhotoEditor().setShowPrimary(showIsPrimaryOption);
422
423        // Note a newly created raw contact gets some random negative ID, so any value is valid
424        // here. (i.e. don't check against -1 or anything.)
425        if (mRawContactIdRequestingPhoto == editor.getRawContactId()) {
426            mCurrentPhotoHandler = photoHandler;
427        }
428    }
429
430    private void addAccountSwitcher(
431            final RawContactDelta currentState, BaseRawContactEditorView editor) {
432        final AccountWithDataSet currentAccount = new AccountWithDataSet(
433                currentState.getAccountName(),
434                currentState.getAccountType(),
435                currentState.getDataSet());
436        final View accountView = editor.findViewById(R.id.account);
437        final View anchorView = editor.findViewById(R.id.account_selector_container);
438        if (accountView == null) {
439            return;
440        }
441        anchorView.setVisibility(View.VISIBLE);
442        accountView.setOnClickListener(new View.OnClickListener() {
443            @Override
444            public void onClick(View v) {
445                final ListPopupWindow popup = new ListPopupWindow(mContext, null);
446                final AccountsListAdapter adapter =
447                        new AccountsListAdapter(mContext,
448                        AccountListFilter.ACCOUNTS_CONTACT_WRITABLE, currentAccount);
449                popup.setWidth(anchorView.getWidth());
450                popup.setAnchorView(anchorView);
451                popup.setAdapter(adapter);
452                popup.setModal(true);
453                popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
454                popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
455                    @Override
456                    public void onItemClick(AdapterView<?> parent, View view, int position,
457                            long id) {
458                        UiClosables.closeQuietly(popup);
459                        AccountWithDataSet newAccount = adapter.getItem(position);
460                        if (!newAccount.equals(currentAccount)) {
461                            rebindEditorsForNewContact(currentState, currentAccount, newAccount);
462                        }
463                    }
464                });
465                popup.show();
466            }
467        });
468    }
469
470    @Override
471    protected boolean doSaveAction(int saveMode) {
472        // Save contact
473        Intent intent = ContactSaveService.createSaveContactIntent(mContext, mState,
474                SAVE_MODE_EXTRA_KEY, saveMode, isEditingUserProfile(),
475                ((Activity)mContext).getClass(), ContactEditorActivity.ACTION_SAVE_COMPLETED,
476                mUpdatedPhotos);
477        mContext.startService(intent);
478
479        // Don't try to save the same photos twice.
480        mUpdatedPhotos = new Bundle();
481
482        return true;
483    }
484
485    @Override
486    public void onSaveInstanceState(Bundle outState) {
487        outState.putSerializable(KEY_EXPANDED_EDITORS, mExpandedEditors);
488        outState.putLong(KEY_RAW_CONTACT_ID_REQUESTING_PHOTO, mRawContactIdRequestingPhoto);
489        outState.putParcelable(KEY_CURRENT_PHOTO_URI, mCurrentPhotoUri);
490        outState.putParcelable(KEY_UPDATED_PHOTOS, mUpdatedPhotos);
491        super.onSaveInstanceState(outState);
492    }
493
494    @Override
495    public void onActivityResult(int requestCode, int resultCode, Intent data) {
496        if (mStatus == Status.SUB_ACTIVITY) {
497            mStatus = Status.EDITING;
498        }
499
500        // See if the photo selection handler handles this result.
501        if (mCurrentPhotoHandler != null && mCurrentPhotoHandler.handlePhotoActivityResult(
502                requestCode, resultCode, data)) {
503            return;
504        }
505
506        super.onActivityResult(requestCode, resultCode, data);
507    }
508
509    @Override
510    protected void joinAggregate(final long contactId) {
511        final Intent intent = ContactSaveService.createJoinContactsIntent(
512                mContext, mContactIdForJoin, contactId, mContactWritableForJoin,
513                ContactEditorActivity.class, ContactEditorActivity.ACTION_JOIN_COMPLETED);
514        mContext.startService(intent);
515    }
516
517    /**
518     * Sets the photo stored in mPhoto and writes it to the RawContact with the given id
519     */
520    private void setPhoto(long rawContact, Bitmap photo, Uri photoUri) {
521        BaseRawContactEditorView requestingEditor = getRawContactEditorView(rawContact);
522
523        if (photo == null || photo.getHeight() <= 0 || photo.getWidth() <= 0) {
524            // This is unexpected.
525            Log.w(TAG, "Invalid bitmap passed to setPhoto()");
526        }
527
528        if (requestingEditor != null) {
529            requestingEditor.setPhotoEntry(photo);
530            // Immediately set all other photos as non-primary. Otherwise the UI can display
531            // multiple photos as "Primary photo".
532            for (int i = 0; i < mContent.getChildCount(); i++) {
533                final View childView = mContent.getChildAt(i);
534                if (childView instanceof BaseRawContactEditorView
535                        && childView != requestingEditor) {
536                    final BaseRawContactEditorView rawContactEditor
537                            = (BaseRawContactEditorView) childView;
538                    rawContactEditor.getPhotoEditor().setSuperPrimary(false);
539                }
540            }
541        } else {
542            Log.w(TAG, "The contact that requested the photo is no longer present.");
543        }
544
545        mUpdatedPhotos.putParcelable(String.valueOf(rawContact), photoUri);
546    }
547
548    /**
549     * Finds raw contact editor view for the given rawContactId.
550     */
551    @Override
552    protected View getAggregationAnchorView(long rawContactId) {
553        BaseRawContactEditorView editorView = getRawContactEditorView(rawContactId);
554        return editorView == null ? null : editorView.findViewById(R.id.anchor_view);
555    }
556
557    public BaseRawContactEditorView getRawContactEditorView(long rawContactId) {
558        for (int i = 0; i < mContent.getChildCount(); i++) {
559            final View childView = mContent.getChildAt(i);
560            if (childView instanceof BaseRawContactEditorView) {
561                final BaseRawContactEditorView editor = (BaseRawContactEditorView) childView;
562                if (editor.getRawContactId() == rawContactId) {
563                    return editor;
564                }
565            }
566        }
567        return null;
568    }
569
570    /**
571     * Returns true if there is currently more than one photo on screen.
572     */
573    private boolean hasMoreThanOnePhoto() {
574        int countWithPicture = 0;
575        final int numEntities = mState.size();
576        for (int i = 0; i < numEntities; i++) {
577            final RawContactDelta entity = mState.get(i);
578            if (entity.isVisible()) {
579                final ValuesDelta primary = entity.getPrimaryEntry(Photo.CONTENT_ITEM_TYPE);
580                if (primary != null && primary.getPhoto() != null) {
581                    countWithPicture++;
582                } else {
583                    final long rawContactId = entity.getRawContactId();
584                    final Uri uri = mUpdatedPhotos.getParcelable(String.valueOf(rawContactId));
585                    if (uri != null) {
586                        try {
587                            mContext.getContentResolver().openInputStream(uri);
588                            countWithPicture++;
589                        } catch (FileNotFoundException e) {
590                        }
591                    }
592                }
593
594                if (countWithPicture > 1) {
595                    return true;
596                }
597            }
598        }
599        return false;
600    }
601
602    /**
603     * Custom photo handler for the editor.  The inner listener that this creates also has a
604     * reference to the editor and acts as an {@link EditorListener}, and uses that editor to hold
605     * state information in several of the listener methods.
606     */
607    private final class PhotoHandler extends PhotoSelectionHandler {
608
609        final long mRawContactId;
610        private final BaseRawContactEditorView mEditor;
611        private final PhotoActionListener mPhotoEditorListener;
612
613        public PhotoHandler(Context context, BaseRawContactEditorView editor, int photoMode,
614                RawContactDeltaList state) {
615            super(context, editor.getPhotoEditor().getChangeAnchorView(), photoMode, false, state);
616            mEditor = editor;
617            mRawContactId = editor.getRawContactId();
618            mPhotoEditorListener = new PhotoEditorListener();
619        }
620
621        @Override
622        public PhotoActionListener getListener() {
623            return mPhotoEditorListener;
624        }
625
626        @Override
627        public void startPhotoActivity(Intent intent, int requestCode, Uri photoUri) {
628            mRawContactIdRequestingPhoto = mEditor.getRawContactId();
629            mCurrentPhotoHandler = this;
630            mStatus = Status.SUB_ACTIVITY;
631            mCurrentPhotoUri = photoUri;
632            ContactEditorFragment.this.startActivityForResult(intent, requestCode);
633        }
634
635        private final class PhotoEditorListener extends PhotoSelectionHandler.PhotoActionListener
636                implements EditorListener {
637
638            @Override
639            public void onRequest(int request) {
640                if (!hasValidState()) return;
641
642                if (request == EditorListener.REQUEST_PICK_PHOTO) {
643                    onClick(mEditor.getPhotoEditor());
644                }
645                if (request == EditorListener.REQUEST_PICK_PRIMARY_PHOTO) {
646                    useAsPrimaryChosen();
647                }
648            }
649
650            @Override
651            public void onDeleteRequested(Editor removedEditor) {
652                // The picture cannot be deleted, it can only be removed, which is handled by
653                // onRemovePictureChosen()
654            }
655
656            /**
657             * User has chosen to set the selected photo as the (super) primary photo
658             */
659            public void useAsPrimaryChosen() {
660                // Set the IsSuperPrimary for each editor
661                int count = mContent.getChildCount();
662                for (int i = 0; i < count; i++) {
663                    final View childView = mContent.getChildAt(i);
664                    if (childView instanceof BaseRawContactEditorView) {
665                        final BaseRawContactEditorView editor =
666                                (BaseRawContactEditorView) childView;
667                        final PhotoEditorView photoEditor = editor.getPhotoEditor();
668                        photoEditor.setSuperPrimary(editor == mEditor);
669                    }
670                }
671                bindEditors();
672            }
673
674            /**
675             * User has chosen to remove a picture
676             */
677            @Override
678            public void onRemovePictureChosen() {
679                mEditor.setPhotoEntry(null);
680
681                // Prevent bitmap from being restored if rotate the device.
682                // (only if we first chose a new photo before removing it)
683                mUpdatedPhotos.remove(String.valueOf(mRawContactId));
684                bindEditors();
685            }
686
687            @Override
688            public void onPhotoSelected(Uri uri) throws FileNotFoundException {
689                final Bitmap bitmap = ContactPhotoUtils.getBitmapFromUri(mContext, uri);
690                setPhoto(mRawContactId, bitmap, uri);
691                mCurrentPhotoHandler = null;
692                bindEditors();
693            }
694
695            @Override
696            public Uri getCurrentPhotoUri() {
697                return mCurrentPhotoUri;
698            }
699
700            @Override
701            public void onPhotoSelectionDismissed() {
702                // Nothing to do.
703            }
704        }
705    }
706}
707