• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ContactEditorFragment()88     public ContactEditorFragment() {
89     }
90 
91     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState)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
onCreate(Bundle savedState)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
onStop()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
onExternalEditorRequest(AccountWithDataSet account, Uri uri)126     public void onExternalEditorRequest(AccountWithDataSet account, Uri uri) {
127         if (mListener != null) {
128             mListener.onCustomEditContactActivityRequested(account, uri, null, false);
129         }
130     }
131 
132     @Override
onOptionsItemSelected(MenuItem item)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
onEditorExpansionChanged()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      */
rebindEditorsForNewContact( RawContactDelta oldState, AccountWithDataSet oldAccount, AccountWithDataSet newAccount)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
setGroupMetaData()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
bindEditors()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 
unsetSuperPrimaryForAllNameEditors()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
getDisplayName()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
getPhoneticName()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      */
updatedExpandedEditorsMap()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      */
updatedPhotoUriForRawContact(long rawContactId)412     private Uri updatedPhotoUriForRawContact(long rawContactId) {
413         return (Uri) mUpdatedPhotos.get(String.valueOf(rawContactId));
414     }
415 
bindPhotoHandler(BaseRawContactEditorView editor, AccountType type, RawContactDeltaList state)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 
addAccountSwitcher( final RawContactDelta currentState, BaseRawContactEditorView editor)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
doSaveAction(int saveMode, boolean backPressed)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
onSaveInstanceState(Bundle outState)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
onActivityResult(int requestCode, int resultCode, Intent data)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
joinAggregate(final long contactId)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      */
setPhoto(long rawContact, Bitmap photo, Uri photoUri)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
getAggregationAnchorView(long rawContactId)581     protected View getAggregationAnchorView(long rawContactId) {
582         BaseRawContactEditorView editorView = getRawContactEditorView(rawContactId);
583         return editorView == null ? null : editorView.findViewById(R.id.anchor_view);
584     }
585 
getRawContactEditorView(long rawContactId)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      */
hasMoreThanOnePhoto()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 
PhotoHandler(Context context, BaseRawContactEditorView editor, int photoMode, RawContactDeltaList state)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
getListener()651         public PhotoActionListener getListener() {
652             return mPhotoEditorListener;
653         }
654 
655         @Override
startPhotoActivity(Intent intent, int requestCode, Uri photoUri)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
onRequest(int request)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
onDeleteRequested(Editor removedEditor)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              */
useAsPrimaryChosen()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
onRemovePictureChosen()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
onPhotoSelected(Uri uri)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
getCurrentPhotoUri()725             public Uri getCurrentPhotoUri() {
726                 return mCurrentPhotoUri;
727             }
728 
729             @Override
onPhotoSelectionDismissed()730             public void onPhotoSelectionDismissed() {
731                 // Nothing to do.
732             }
733         }
734     }
735 }
736