• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.contacts.editor;
18 
19 import com.android.contacts.ContactSaveService;
20 import com.android.contacts.R;
21 import com.android.contacts.activities.CompactContactEditorActivity;
22 import com.android.contacts.common.model.AccountTypeManager;
23 import com.android.contacts.common.model.RawContactDelta;
24 import com.android.contacts.common.model.RawContactDeltaList;
25 import com.android.contacts.common.model.ValuesDelta;
26 import com.android.contacts.common.model.account.AccountType;
27 import com.android.contacts.common.util.ImplicitIntentsUtil;
28 import com.android.contacts.detail.PhotoSelectionHandler;
29 import com.android.contacts.util.ContactPhotoUtils;
30 
31 import android.app.Activity;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.graphics.Bitmap;
35 import android.net.Uri;
36 import android.os.Bundle;
37 import android.util.Log;
38 import android.view.LayoutInflater;
39 import android.view.MenuItem;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.widget.LinearLayout;
43 
44 import java.io.FileNotFoundException;
45 
46 /**
47  * Contact editor with only the most important fields displayed initially.
48  */
49 public class CompactContactEditorFragment extends ContactEditorBaseFragment implements
50         CompactRawContactsEditorView.Listener, PhotoSourceDialogFragment.Listener {
51 
52     private static final String KEY_PHOTO_URI = "photo_uri";
53     private static final String KEY_PHOTO_RAW_CONTACT_ID = "photo_raw_contact_id";
54 
55     /**
56      * Displays a PopupWindow with photo edit options.
57      */
58     final class PhotoHandler extends PhotoSelectionHandler implements View.OnClickListener {
59 
60         /**
61          * Receiver of photo edit option callbacks.
62          */
63         private final class PhotoListener extends PhotoActionListener {
64 
65             @Override
onRemovePictureChosen()66             public void onRemovePictureChosen() {
67                 getContent().setPhoto(/* bitmap =*/ null);
68                 mUpdatedPhotos.remove(String.valueOf(mPhotoRawContactId));
69 
70                 // Update the mode so the options change if user clicks the photo again
71                 mPhotoMode = getPhotoMode();
72             }
73 
74             @Override
onPhotoSelected(Uri uri)75             public void onPhotoSelected(Uri uri) throws FileNotFoundException {
76                 final Bitmap bitmap = ContactPhotoUtils.getBitmapFromUri(getActivity(), uri);
77                 if (bitmap == null || bitmap.getHeight() <= 0 || bitmap.getWidth() <= 0) {
78                     Log.w(TAG, "Invalid photo selected");
79                 }
80                 getContent().setPhoto(bitmap);
81 
82                 // Clear any previously saved full resolution photos under negative raw contact IDs
83                 // so that we will use the newly selected photo, instead of an old one on rotations.
84                 removeNewRawContactPhotos();
85 
86                 // If a new photo was chosen but not yet saved,
87                 // we need to update the UI immediately
88                 mUpdatedPhotos.putParcelable(String.valueOf(mPhotoRawContactId), uri);
89                 getContent().setFullSizePhoto(uri);
90 
91                 // Update the mode so the options change if user clicks the photo again
92                 mPhotoMode = getPhotoMode();
93 
94                 // Re-create the photo handler so that any additional photo selections create a
95                 // new temp file (and don't hit the one that was just added to the cache).
96                 mPhotoHandler = createPhotoHandler();
97             }
98 
99             @Override
getCurrentPhotoUri()100             public Uri getCurrentPhotoUri() {
101                 return mPhotoUri;
102             }
103 
104             @Override
onPhotoSelectionDismissed()105             public void onPhotoSelectionDismissed() {
106             }
107         }
108 
109         private PhotoListener mPhotoListener;
110         private int mPhotoMode;
111 
PhotoHandler(Context context, int photoMode, RawContactDeltaList state)112         public PhotoHandler(Context context, int photoMode, RawContactDeltaList state) {
113             // We pass a null changeAnchorView since we are overriding onClick so that we
114             // can show the photo options in a dialog instead of a ListPopupWindow (which would
115             // be anchored at changeAnchorView).
116             super(context, /* changeAnchorView =*/ null, photoMode, /* isDirectoryContact =*/ false,
117                     state);
118             mPhotoListener = new PhotoListener();
119             mPhotoMode = photoMode;
120         }
121 
122         @Override
onClick(View view)123         public void onClick(View view) {
124             PhotoSourceDialogFragment.show(CompactContactEditorFragment.this, mPhotoMode);
125         }
126 
127         @Override
getListener()128         public PhotoActionListener getListener() {
129             return mPhotoListener;
130         }
131 
132         @Override
startPhotoActivity(Intent intent, int requestCode, Uri photoUri)133         protected void startPhotoActivity(Intent intent, int requestCode, Uri photoUri) {
134             mPhotoUri = photoUri;
135             mStatus = Status.SUB_ACTIVITY;
136 
137             CompactContactEditorFragment.this.startActivityForResult(intent, requestCode);
138         }
139     }
140 
141     private PhotoHandler mPhotoHandler;
142     private Uri mPhotoUri;
143     private long mPhotoRawContactId;
144 
145     @Override
onCreate(Bundle savedState)146     public void onCreate(Bundle savedState) {
147         super.onCreate(savedState);
148 
149         if (savedState != null) {
150             mPhotoUri = savedState.getParcelable(KEY_PHOTO_URI);
151             mPhotoRawContactId = savedState.getLong(KEY_PHOTO_RAW_CONTACT_ID);
152         }
153     }
154 
155     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState)156     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
157         setHasOptionsMenu(true);
158 
159         final View view = inflater.inflate(
160                 R.layout.compact_contact_editor_fragment, container, false);
161         mContent = (LinearLayout) view.findViewById(R.id.editors);
162         return view;
163     }
164 
165     @Override
onSaveInstanceState(Bundle outState)166     public void onSaveInstanceState(Bundle outState) {
167         outState.putParcelable(KEY_PHOTO_URI, mPhotoUri);
168         outState.putLong(KEY_PHOTO_RAW_CONTACT_ID, mPhotoRawContactId);
169         super.onSaveInstanceState(outState);
170     }
171 
172     @Override
onActivityResult(int requestCode, int resultCode, Intent data)173     public void onActivityResult(int requestCode, int resultCode, Intent data) {
174         if (mStatus == Status.SUB_ACTIVITY) {
175             mStatus = Status.EDITING;
176         }
177         if (mPhotoHandler != null
178                 && mPhotoHandler.handlePhotoActivityResult(requestCode, resultCode, data)) {
179             return;
180         }
181         super.onActivityResult(requestCode, resultCode, data);
182     }
183 
184     @Override
onStop()185     public void onStop() {
186         super.onStop();
187 
188         // If anything was left unsaved, save it now
189         if (!getActivity().isChangingConfigurations() && mStatus == Status.EDITING) {
190             save(SaveMode.RELOAD, /* backPressed =*/ false);
191         }
192     }
193 
194     @Override
onOptionsItemSelected(MenuItem item)195     public boolean onOptionsItemSelected(MenuItem item) {
196         if (item.getItemId() == android.R.id.home) {
197             return revert();
198         }
199         return super.onOptionsItemSelected(item);
200     }
201 
202     @Override
bindEditors()203     protected void bindEditors() {
204         if (!isReadyToBindEditors()) {
205             return;
206         }
207 
208         // Add input fields for the loaded Contact
209         final CompactRawContactsEditorView editorView = getContent();
210         editorView.setListener(this);
211         editorView.setState(mState, getMaterialPalette(), mViewIdGenerator, mPhotoId, mNameId,
212                 mReadOnlyDisplayName, mHasNewContact, mIsUserProfile);
213         if (mReadOnlyDisplayName != null) {
214             mReadOnlyNameEditorView = editorView.getDefaultNameEditorView();
215         }
216 
217         // Set up the photo widget
218         mPhotoHandler = createPhotoHandler();
219         mPhotoRawContactId = editorView.getPhotoRawContactId();
220         if (mPhotoRawContactId < 0) {
221             // Since the raw contact IDs for new contacts are random negative numbers
222             // we consider any negative key a match
223             for (String key : mUpdatedPhotos.keySet()) {
224                 try {
225                     if (Integer.parseInt(key) < 0) {
226                         editorView.setFullSizePhoto((Uri) mUpdatedPhotos.getParcelable(key));
227                         break;
228                     }
229                 } catch (NumberFormatException ignored) {
230                 }
231             }
232         } else if (mUpdatedPhotos.containsKey(String.valueOf(mPhotoRawContactId))) {
233             editorView.setFullSizePhoto((Uri) mUpdatedPhotos.getParcelable(
234                     String.valueOf(mPhotoRawContactId)));
235         }
236         editorView.setPhotoHandler(mPhotoHandler);
237 
238         // The editor is ready now so make it visible
239         editorView.setEnabled(isEnabled());
240         editorView.setVisibility(View.VISIBLE);
241 
242         // Refresh the ActionBar as the visibility of the join command
243         // Activity can be null if we have been detached from the Activity.
244         invalidateOptionsMenu();
245     }
246 
isReadyToBindEditors()247     private boolean isReadyToBindEditors() {
248         if (mState.isEmpty()) {
249             if (Log.isLoggable(TAG, Log.VERBOSE)) {
250                 Log.v(TAG, "No data to bind editors");
251             }
252             return false;
253         }
254         if (mIsEdit && !mExistingContactDataReady) {
255             if (Log.isLoggable(TAG, Log.VERBOSE)) {
256                 Log.v(TAG, "Existing contact data is not ready to bind editors.");
257             }
258             return false;
259         }
260         if (mHasNewContact && !mNewContactDataReady) {
261             if (Log.isLoggable(TAG, Log.VERBOSE)) {
262                 Log.v(TAG, "New contact data is not ready to bind editors.");
263             }
264             return false;
265         }
266         return true;
267     }
268 
createPhotoHandler()269     private PhotoHandler createPhotoHandler() {
270         return new PhotoHandler(getActivity(), getPhotoMode(), mState);
271     }
272 
getPhotoMode()273     private int getPhotoMode() {
274         // To determine the options that are available to the user to update their photo
275         // (i.e. the photo mode), check if any of the writable raw contacts has a photo set
276         Integer photoMode = null;
277         boolean hasWritableAccountType = false;
278         final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
279         for (RawContactDelta rawContactDelta : mState) {
280             if (!rawContactDelta.isVisible()) {
281                 continue;
282             }
283             final AccountType accountType = rawContactDelta.getAccountType(accountTypes);
284             if (accountType.areContactsWritable()) {
285                 hasWritableAccountType = true;
286                 if (getContent().isWritablePhotoSet()) {
287                     photoMode = PhotoActionPopup.Modes.MULTIPLE_WRITE_ABLE_PHOTOS;
288                     break;
289                 }
290             }
291         }
292         // If the mode was not set, base it on whether we saw a writable contact or not
293         if (photoMode == null) {
294             photoMode = hasWritableAccountType
295                     ? PhotoActionPopup.Modes.NO_PHOTO : PhotoActionPopup.Modes.READ_ONLY_PHOTO;
296         }
297         return photoMode;
298     }
299 
300     @Override
getAggregationAnchorView(long rawContactId)301     protected View getAggregationAnchorView(long rawContactId) {
302         return getContent().getAggregationAnchorView();
303     }
304 
305     @Override
setGroupMetaData()306     protected void setGroupMetaData() {
307         // The compact editor does not support groups.
308     }
309 
310     @Override
doSaveAction(int saveMode, boolean backPressed)311     protected boolean doSaveAction(int saveMode, boolean backPressed) {
312         // Save contact. No need to pass the palette since we are finished editing after the save.
313         final Intent intent = ContactSaveService.createSaveContactIntent(mContext, mState,
314                 SAVE_MODE_EXTRA_KEY, saveMode, isEditingUserProfile(),
315                 ((Activity) mContext).getClass(),
316                 CompactContactEditorActivity.ACTION_SAVE_COMPLETED, mUpdatedPhotos, backPressed);
317         mContext.startService(intent);
318 
319         return true;
320     }
321 
322     @Override
joinAggregate(final long contactId)323     protected void joinAggregate(final long contactId) {
324         final Intent intent = ContactSaveService.createJoinContactsIntent(
325                 mContext, mContactIdForJoin, contactId, CompactContactEditorActivity.class,
326                 CompactContactEditorActivity.ACTION_JOIN_COMPLETED);
327         mContext.startService(intent);
328     }
329 
330     @Override
onRemovePictureChosen()331     public void onRemovePictureChosen() {
332         if (mPhotoHandler != null) {
333             mPhotoHandler.getListener().onRemovePictureChosen();
334         }
335     }
336 
337     @Override
onTakePhotoChosen()338     public void onTakePhotoChosen() {
339         if (mPhotoHandler != null) {
340             mPhotoHandler.getListener().onTakePhotoChosen();
341         }
342     }
343 
344     @Override
onPickFromGalleryChosen()345     public void onPickFromGalleryChosen() {
346         if (mPhotoHandler != null) {
347             mPhotoHandler.getListener().onPickFromGalleryChosen();
348         }
349     }
350 
351     @Override
onExpandEditor()352     public void onExpandEditor() {
353         // Determine if this is an insert (new contact) or edit
354         final boolean isInsert = isInsert(getActivity().getIntent());
355 
356         if (isInsert) {
357             // For inserts, prevent any changes from being saved when the base fragment is destroyed
358             mStatus = Status.CLOSING;
359         } else if (hasPendingRawContactChanges()) {
360             // Save whatever is in the form
361             save(SaveMode.CLOSE, /* backPressed =*/ false);
362         }
363 
364         // Prepare an Intent to start the expanded editor
365         final Intent intent = isInsert
366                 ? EditorIntents.createInsertContactIntent(
367                         mState, getDisplayName(), getPhoneticName(), mUpdatedPhotos)
368                 : EditorIntents.createEditContactIntent(mLookupUri, getMaterialPalette(),
369                         mPhotoId, mNameId);
370         ImplicitIntentsUtil.startActivityInApp(getActivity(), intent);
371 
372         getActivity().finish();
373     }
374 
375     @Override
onNameFieldChanged(long rawContactId, ValuesDelta valuesDelta)376     public void onNameFieldChanged(long rawContactId, ValuesDelta valuesDelta) {
377         final Activity activity = getActivity();
378         if (activity == null || activity.isFinishing()) {
379             return;
380         }
381         if (!mIsUserProfile) {
382             acquireAggregationSuggestions(activity, rawContactId, valuesDelta);
383         }
384     }
385 
386     @Override
getDisplayName()387     public String getDisplayName() {
388         final StructuredNameEditorView structuredNameEditorView =
389                 getContent().getStructuredNameEditorView();
390         return structuredNameEditorView == null
391                 ? null : structuredNameEditorView.getDisplayName();
392     }
393 
394     @Override
getPhoneticName()395     public String getPhoneticName() {
396         final PhoneticNameEditorView phoneticNameEditorView =
397                 getContent().getFirstPhoneticNameEditorView();
398         return phoneticNameEditorView == null
399                 ? null : phoneticNameEditorView.getPhoneticName();
400     }
401 
getContent()402     private CompactRawContactsEditorView getContent() {
403         return (CompactRawContactsEditorView) mContent;
404     }
405 }
406