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