Home | History | Annotate | Download | only in editor
      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 
     17 package com.android.contacts.editor;
     18 
     19 import android.app.Activity;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.graphics.Bitmap;
     23 import android.net.Uri;
     24 import android.os.Bundle;
     25 import android.provider.ContactsContract.CommonDataKinds.Photo;
     26 import android.text.TextUtils;
     27 import android.util.Log;
     28 import android.view.LayoutInflater;
     29 import android.view.MenuItem;
     30 import android.view.View;
     31 import android.view.ViewGroup;
     32 import android.widget.AdapterView;
     33 import android.widget.LinearLayout;
     34 import android.widget.ListPopupWindow;
     35 
     36 import com.android.contacts.ContactSaveService;
     37 import com.android.contacts.R;
     38 import com.android.contacts.activities.ContactEditorActivity;
     39 import com.android.contacts.common.model.AccountTypeManager;
     40 import com.android.contacts.common.model.RawContactDelta;
     41 import com.android.contacts.common.model.RawContactDeltaList;
     42 import com.android.contacts.common.model.ValuesDelta;
     43 import com.android.contacts.common.model.account.AccountType;
     44 import com.android.contacts.common.model.account.AccountWithDataSet;
     45 import com.android.contacts.common.util.AccountsListAdapter;
     46 import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter;
     47 import com.android.contacts.detail.PhotoSelectionHandler;
     48 import com.android.contacts.editor.Editor.EditorListener;
     49 import com.android.contacts.util.ContactPhotoUtils;
     50 import com.android.contacts.util.UiClosables;
     51 
     52 import java.io.FileNotFoundException;
     53 import java.util.Collections;
     54 import java.util.HashMap;
     55 import java.util.List;
     56 
     57 /**
     58  * Contact editor with all fields displayed.
     59  */
     60 public 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