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