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