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