1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.contacts.editor; 18 19import com.android.contacts.ContactSaveService; 20import com.android.contacts.R; 21import com.android.contacts.activities.CompactContactEditorActivity; 22import com.android.contacts.common.model.RawContactDelta; 23import com.android.contacts.common.model.ValuesDelta; 24import com.android.contacts.common.model.account.AccountWithDataSet; 25import com.android.contacts.util.ContactPhotoUtils; 26 27import android.app.Activity; 28import android.content.Intent; 29import android.graphics.Bitmap; 30import android.net.Uri; 31import android.os.Bundle; 32import android.text.TextUtils; 33import android.util.Log; 34import android.view.LayoutInflater; 35import android.view.MenuItem; 36import android.view.View; 37import android.view.ViewGroup; 38import android.widget.LinearLayout; 39import android.widget.Toast; 40 41import java.io.FileNotFoundException; 42import java.util.ArrayList; 43 44/** 45 * Contact editor with only the most important fields displayed initially. 46 */ 47public class CompactContactEditorFragment extends ContactEditorBaseFragment implements 48 CompactRawContactsEditorView.Listener, CompactPhotoEditorView.Listener { 49 50 private static final String KEY_PHOTO_RAW_CONTACT_ID = "photo_raw_contact_id"; 51 private static final String KEY_UPDATED_PHOTOS = "updated_photos"; 52 53 private long mPhotoRawContactId; 54 private Bundle mUpdatedPhotos = new Bundle(); 55 56 @Override 57 public void onCreate(Bundle savedState) { 58 super.onCreate(savedState); 59 60 if (savedState != null) { 61 mPhotoRawContactId = savedState.getLong(KEY_PHOTO_RAW_CONTACT_ID); 62 mUpdatedPhotos = savedState.getParcelable(KEY_UPDATED_PHOTOS); 63 } 64 } 65 66 @Override 67 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { 68 setHasOptionsMenu(true); 69 70 final View view = inflater.inflate( 71 R.layout.compact_contact_editor_fragment, container, false); 72 mContent = (LinearLayout) view.findViewById(R.id.raw_contacts_editor_view); 73 return view; 74 } 75 76 @Override 77 public void onSaveInstanceState(Bundle outState) { 78 outState.putLong(KEY_PHOTO_RAW_CONTACT_ID, mPhotoRawContactId); 79 outState.putParcelable(KEY_UPDATED_PHOTOS, mUpdatedPhotos); 80 super.onSaveInstanceState(outState); 81 } 82 83 @Override 84 public boolean onOptionsItemSelected(MenuItem item) { 85 if (item.getItemId() == android.R.id.home) { 86 return revert(); 87 } 88 return super.onOptionsItemSelected(item); 89 } 90 91 @Override 92 protected void bindEditors() { 93 if (!isReadyToBindEditors()) { 94 return; 95 } 96 97 // Add input fields for the loaded Contact 98 final CompactRawContactsEditorView editorView = getContent(); 99 editorView.setListener(this); 100 editorView.setState(mState, getMaterialPalette(), mViewIdGenerator, mPhotoId, 101 mHasNewContact, mIsUserProfile, mAccountWithDataSet); 102 if (mHasNewContact && !TextUtils.isEmpty(mReadOnlyDisplayName)) { 103 mReadOnlyNameEditorView = editorView.getPrimaryNameEditorView(); 104 editorView.maybeSetReadOnlyDisplayNameAsPrimary(mReadOnlyDisplayName); 105 } 106 107 // Set up the photo widget 108 editorView.setPhotoListener(this); 109 mPhotoRawContactId = editorView.getPhotoRawContactId(); 110 // If there is an updated full resolution photo apply it now, this will be the case if 111 // the user selects or takes a new photo, then rotates the device. 112 final Uri uri = (Uri) mUpdatedPhotos.get(String.valueOf(mPhotoRawContactId)); 113 if (uri != null) { 114 editorView.setFullSizePhoto(uri); 115 } 116 117 // The editor is ready now so make it visible 118 editorView.setEnabled(isEnabled()); 119 editorView.setVisibility(View.VISIBLE); 120 121 // Refresh the ActionBar as the visibility of the join command 122 // Activity can be null if we have been detached from the Activity. 123 invalidateOptionsMenu(); 124 } 125 126 private boolean isReadyToBindEditors() { 127 if (mState.isEmpty()) { 128 if (Log.isLoggable(TAG, Log.VERBOSE)) { 129 Log.v(TAG, "No data to bind editors"); 130 } 131 return false; 132 } 133 if (mIsEdit && !mExistingContactDataReady) { 134 if (Log.isLoggable(TAG, Log.VERBOSE)) { 135 Log.v(TAG, "Existing contact data is not ready to bind editors."); 136 } 137 return false; 138 } 139 if (mHasNewContact && !mNewContactDataReady) { 140 if (Log.isLoggable(TAG, Log.VERBOSE)) { 141 Log.v(TAG, "New contact data is not ready to bind editors."); 142 } 143 return false; 144 } 145 return true; 146 } 147 148 @Override 149 protected View getAggregationAnchorView(long rawContactId) { 150 return getContent().getAggregationAnchorView(); 151 } 152 153 @Override 154 protected void setGroupMetaData() { 155 if (mGroupMetaData != null) { 156 getContent().setGroupMetaData(mGroupMetaData); 157 } 158 } 159 160 @Override 161 protected boolean doSaveAction(int saveMode, Long joinContactId) { 162 final Intent intent = ContactSaveService.createSaveContactIntent(mContext, mState, 163 SAVE_MODE_EXTRA_KEY, saveMode, isEditingUserProfile(), 164 ((Activity) mContext).getClass(), 165 CompactContactEditorActivity.ACTION_SAVE_COMPLETED, mUpdatedPhotos, 166 JOIN_CONTACT_ID_EXTRA_KEY, joinContactId); 167 return startSaveService(mContext, intent, saveMode); 168 } 169 170 @Override 171 protected void joinAggregate(final long contactId) { 172 final Intent intent = ContactSaveService.createJoinContactsIntent( 173 mContext, mContactIdForJoin, contactId, CompactContactEditorActivity.class, 174 CompactContactEditorActivity.ACTION_JOIN_COMPLETED); 175 mContext.startService(intent); 176 } 177 178 public void removePhoto() { 179 getContent().removePhoto(); 180 mUpdatedPhotos.remove(String.valueOf(mPhotoRawContactId)); 181 } 182 183 public void updatePhoto(Uri uri) throws FileNotFoundException { 184 final Bitmap bitmap = ContactPhotoUtils.getBitmapFromUri(getActivity(), uri); 185 if (bitmap == null || bitmap.getHeight() <= 0 || bitmap.getWidth() <= 0) { 186 Toast.makeText(mContext, R.string.contactPhotoSavedErrorToast, 187 Toast.LENGTH_SHORT).show(); 188 return; 189 } 190 mUpdatedPhotos.putParcelable(String.valueOf(mPhotoRawContactId), uri); 191 getContent().updatePhoto(uri); 192 } 193 194 public void setPrimaryPhoto(CompactPhotoSelectionFragment.Photo photo) { 195 getContent().setPrimaryPhoto(photo); 196 197 // Update the photo ID we will try to match when selecting the photo to display 198 mPhotoId = photo.photoId; 199 } 200 201 @Override 202 public void onNameFieldChanged(long rawContactId, ValuesDelta valuesDelta) { 203 final Activity activity = getActivity(); 204 if (activity == null || activity.isFinishing()) { 205 return; 206 } 207 acquireAggregationSuggestions(activity, rawContactId, valuesDelta); 208 } 209 210 @Override 211 public void onRebindEditorsForNewContact(RawContactDelta oldState, 212 AccountWithDataSet oldAccount, AccountWithDataSet newAccount) { 213 mNewContactAccountChanged = true; 214 mAccountWithDataSet = newAccount; 215 rebindEditorsForNewContact(oldState, oldAccount, newAccount); 216 } 217 218 @Override 219 public void onBindEditorsFailed() { 220 final Activity activity = getActivity(); 221 if (activity != null && !activity.isFinishing()) { 222 Toast.makeText(activity, R.string.compact_editor_failed_to_load, 223 Toast.LENGTH_SHORT).show(); 224 activity.setResult(Activity.RESULT_CANCELED); 225 activity.finish(); 226 } 227 } 228 229 @Override 230 public void onEditorsBound() { 231 getLoaderManager().initLoader(LOADER_GROUPS, null, mGroupsLoaderListener); 232 } 233 234 @Override 235 public void onPhotoEditorViewClicked() { 236 if (isEditingMultipleRawContacts()) { 237 final ArrayList<CompactPhotoSelectionFragment.Photo> photos = getContent().getPhotos(); 238 if (photos.size() > 1) { 239 updatePrimaryForSelection(photos); 240 // For aggregate contacts, the user may select a new super primary photo from among 241 // the (non-default) raw contact photos, or source a new photo. 242 getEditorActivity().selectPhoto(photos, getPhotoMode()); 243 return; 244 } 245 } 246 // For contacts composed of a single writable raw contact, or raw contacts have no more 247 // than 1 photo, clicking the photo view simply opens the source photo dialog 248 getEditorActivity().changePhoto(getPhotoMode()); 249 } 250 251 // This method override photo's primary flag based on photoId and set the photo currently 252 // shown in the editor to be the new primary no matter how many primary photos there are in 253 // the photo picker. This is because the photos returned by "getPhoto" may contain 0, 1, 254 // or 2+ primary photos and when we link contacts in the editor, the photos returned may change. 255 // We need to put check mark on the photo currently shown in editor, so we override "primary". 256 // This doesn't modify anything in the database,so there would be no pending changes. 257 private void updatePrimaryForSelection(ArrayList<CompactPhotoSelectionFragment.Photo> photos) { 258 for (CompactPhotoSelectionFragment.Photo photo : photos) { 259 if (photo.photoId == mPhotoId) { 260 photo.primary = true; 261 } else { 262 photo.primary = false; 263 } 264 updateContentDescription(photo); 265 } 266 } 267 268 private void updateContentDescription(CompactPhotoSelectionFragment.Photo photo) { 269 if (!TextUtils.isEmpty(photo.accountType)) { 270 photo.contentDescription = getResources().getString(photo.primary ? 271 R.string.photo_view_description_checked : 272 R.string.photo_view_description_not_checked, 273 photo.accountType, photo.accountName); 274 photo.contentDescriptionChecked = getResources().getString( 275 R.string.photo_view_description_checked, 276 photo.accountType, photo.accountName); 277 } else { 278 photo.contentDescription = getResources().getString(photo.primary ? 279 R.string.photo_view_description_checked_no_info : 280 R.string.photo_view_description_not_checked_no_info); 281 photo.contentDescriptionChecked = getResources().getString( 282 R.string.photo_view_description_checked_no_info); 283 } 284 } 285 286 @Override 287 public void onRawContactSelected(Uri uri, long rawContactId, boolean isReadOnly) { 288 final Activity activity = getActivity(); 289 if (activity != null && !activity.isFinishing()) { 290 final Intent intent = EditorIntents.createEditContactIntentForRawContact( 291 activity, uri, rawContactId, isReadOnly); 292 activity.startActivity(intent); 293 } 294 } 295 296 @Override 297 public Bundle getUpdatedPhotos() { 298 return mUpdatedPhotos; 299 } 300 301 private int getPhotoMode() { 302 if (getContent().isWritablePhotoSet()) { 303 return isEditingMultipleRawContacts() 304 ? PhotoActionPopup.Modes.MULTIPLE_WRITE_ABLE_PHOTOS 305 : PhotoActionPopup.Modes.WRITE_ABLE_PHOTO; 306 } 307 return PhotoActionPopup.Modes.NO_PHOTO; 308 } 309 310 private CompactContactEditorActivity getEditorActivity() { 311 return (CompactContactEditorActivity) getActivity(); 312 } 313 314 private CompactRawContactsEditorView getContent() { 315 return (CompactRawContactsEditorView) mContent; 316 } 317} 318