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