1/*
2 * Copyright (C) 2009 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 android.content.Context;
20import android.graphics.Bitmap;
21import android.graphics.BitmapFactory;
22import android.net.Uri;
23import android.provider.ContactsContract.CommonDataKinds.Photo;
24import android.provider.ContactsContract.DisplayPhoto;
25import android.util.AttributeSet;
26import android.view.View;
27import android.widget.Button;
28import android.widget.ImageView;
29import android.widget.LinearLayout;
30import android.widget.RadioButton;
31
32import com.android.contacts.R;
33import com.android.contacts.common.ContactPhotoManager.DefaultImageProvider;
34import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
35import com.android.contacts.common.model.RawContactDelta;
36import com.android.contacts.common.ContactPhotoManager;
37import com.android.contacts.common.ContactsUtils;
38import com.android.contacts.common.model.ValuesDelta;
39import com.android.contacts.common.model.dataitem.DataKind;
40import com.android.contacts.util.ContactPhotoUtils;
41
42/**
43 * Simple editor for {@link Photo}.
44 */
45public class PhotoEditorView extends LinearLayout implements Editor {
46
47    private ImageView mPhotoImageView;
48    private Button mChangeButton;
49    private RadioButton mPrimaryCheckBox;
50
51    private ValuesDelta mEntry;
52    private EditorListener mListener;
53    private ContactPhotoManager mContactPhotoManager;
54
55    private boolean mHasSetPhoto = false;
56
57    public PhotoEditorView(Context context) {
58        super(context);
59    }
60
61    public PhotoEditorView(Context context, AttributeSet attrs) {
62        super(context, attrs);
63    }
64
65    @Override
66    public void setEnabled(boolean enabled) {
67        super.setEnabled(enabled);
68    }
69
70    @Override
71    public void editNewlyAddedField() {
72        // Never called, since the user never adds a new photo-editor;
73        // you can only change the picture in an existing editor.
74    }
75
76    /** {@inheritDoc} */
77    @Override
78    protected void onFinishInflate() {
79        super.onFinishInflate();
80        mContactPhotoManager = ContactPhotoManager.getInstance(getContext());
81        mPhotoImageView = (ImageView) findViewById(R.id.photo);
82        mPrimaryCheckBox = (RadioButton) findViewById(R.id.primary_checkbox);
83        mChangeButton = (Button) findViewById(R.id.change_button);
84        mPrimaryCheckBox = (RadioButton) findViewById(R.id.primary_checkbox);
85        if (mChangeButton != null) {
86            mChangeButton.setOnClickListener(new OnClickListener() {
87                @Override
88                public void onClick(View v) {
89                    if (mListener != null) {
90                        mListener.onRequest(EditorListener.REQUEST_PICK_PHOTO);
91                    }
92                }
93            });
94        }
95        // Turn off own state management. We do this ourselves on rotation.
96        mPrimaryCheckBox.setSaveEnabled(false);
97        mPrimaryCheckBox.setOnClickListener(new OnClickListener() {
98            @Override
99            public void onClick(View v) {
100                if (mListener != null) {
101                    mListener.onRequest(EditorListener.REQUEST_PICK_PRIMARY_PHOTO);
102                }
103            }
104        });
105    }
106
107    /** {@inheritDoc} */
108    @Override
109    public void onFieldChanged(String column, String value) {
110        throw new UnsupportedOperationException("Photos don't support direct field changes");
111    }
112
113    /** {@inheritDoc} */
114    @Override
115    public void setValues(DataKind kind, ValuesDelta values, RawContactDelta state, boolean readOnly,
116            ViewIdGenerator vig) {
117        mEntry = values;
118
119        setId(vig.getId(state, kind, values, 0));
120
121        mPrimaryCheckBox.setChecked(values != null && values.isSuperPrimary());
122
123        if (values != null) {
124            // Try decoding photo if actual entry
125            final byte[] photoBytes = values.getAsByteArray(Photo.PHOTO);
126            if (photoBytes != null) {
127                final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0,
128                        photoBytes.length);
129
130                mPhotoImageView.setImageBitmap(photo);
131                mHasSetPhoto = true;
132                mEntry.setFromTemplate(false);
133
134                if (values.getAfter() == null || values.getAfter().get(Photo.PHOTO) == null) {
135                    // If the user hasn't updated the PHOTO value, then PHOTO_FILE_ID may contain
136                    // a reference to a larger version of PHOTO that we can bind to the UI.
137                    // Otherwise, we need to wait for a call to #setFullSizedPhoto() to update
138                    // our full sized image.
139                    final Integer photoFileId = values.getAsInteger(Photo.PHOTO_FILE_ID);
140                    if (photoFileId != null) {
141                        final Uri photoUri = DisplayPhoto.CONTENT_URI.buildUpon()
142                                .appendPath(photoFileId.toString()).build();
143                        setFullSizedPhoto(photoUri);
144                    }
145                }
146
147            } else {
148                resetDefault();
149            }
150        } else {
151            resetDefault();
152        }
153    }
154
155    /**
156     * Whether to display a "Primary photo" RadioButton. This is only needed if there are multiple
157     * candidate photos.
158     */
159    public void setShowPrimary(boolean showPrimaryCheckBox) {
160        mPrimaryCheckBox.setVisibility(showPrimaryCheckBox ? View.VISIBLE : View.GONE);
161    }
162
163    /**
164     * Return true if a valid {@link Photo} has been set.
165     */
166    public boolean hasSetPhoto() {
167        return mHasSetPhoto;
168    }
169
170    /**
171     * Assign the given {@link Bitmap} as the new value for the sake of building
172     * {@link ValuesDelta}. We may as well bind a thumbnail to the UI while we are at it.
173     */
174    public void setPhotoEntry(Bitmap photo) {
175        if (photo == null) {
176            // Clear any existing photo and return
177            mEntry.put(Photo.PHOTO, (byte[])null);
178            resetDefault();
179            return;
180        }
181
182        final int size = ContactsUtils.getThumbnailSize(getContext());
183        final Bitmap scaled = Bitmap.createScaledBitmap(photo, size, size, false);
184
185        mPhotoImageView.setImageBitmap(scaled);
186        mHasSetPhoto = true;
187        mEntry.setFromTemplate(false);
188
189        // When the user chooses a new photo mark it as super primary
190        mEntry.setSuperPrimary(true);
191
192        // Even though high-res photos cannot be saved by passing them via
193        // an EntityDeltaList (since they cause the Bundle size limit to be
194        // exceeded), we still pass a low-res thumbnail. This simplifies
195        // code all over the place, because we don't have to test whether
196        // there is a change in EITHER the delta-list OR a changed photo...
197        // this way, there is always a change in the delta-list.
198        final byte[] compressed = ContactPhotoUtils.compressBitmap(scaled);
199        if (compressed != null) {
200            mEntry.setPhoto(compressed);
201        }
202    }
203
204    /**
205     * Bind the {@param photoUri}'s photo to editor's UI. This doesn't affect {@link ValuesDelta}.
206     */
207    public void setFullSizedPhoto(Uri photoUri) {
208        if (photoUri != null) {
209            final DefaultImageProvider fallbackToPreviousImage = new DefaultImageProvider() {
210                @Override
211                public void applyDefaultImage(ImageView view, int extent, boolean darkTheme,
212                        DefaultImageRequest defaultImageRequest) {
213                    // Before we finish setting the full sized image, don't change the current
214                    // image that is set in any way.
215                }
216            };
217            mContactPhotoManager.loadPhoto(mPhotoImageView, photoUri,
218                    mPhotoImageView.getWidth(), /* darkTheme = */ false, /* isCircular = */ false,
219                    /* defaultImageRequest = */ null, fallbackToPreviousImage);
220        }
221    }
222
223    /**
224     * Set the super primary bit on the photo.
225     */
226    public void setSuperPrimary(boolean superPrimary) {
227        mEntry.put(Photo.IS_SUPER_PRIMARY, superPrimary ? 1 : 0);
228    }
229
230    protected void resetDefault() {
231        // Invalid photo, show default "add photo" place-holder
232        mPhotoImageView.setImageDrawable(
233                ContactPhotoManager.getDefaultAvatarDrawableForContact(getResources(), false, null));
234        mHasSetPhoto = false;
235        mEntry.setFromTemplate(true);
236    }
237
238    /** {@inheritDoc} */
239    @Override
240    public void setEditorListener(EditorListener listener) {
241        mListener = listener;
242    }
243
244    @Override
245    public void setDeletable(boolean deletable) {
246        // Photo is not deletable
247    }
248
249    @Override
250    public boolean isEmpty() {
251        return !mHasSetPhoto;
252    }
253
254    @Override
255    public void markDeleted() {
256        // Photo is not deletable
257    }
258
259    @Override
260    public void deleteEditor() {
261        // Photo is not deletable
262    }
263
264    @Override
265    public void clearAllFields() {
266        resetDefault();
267    }
268
269    /**
270     * The change drop down menu should be anchored to this view.
271     */
272    public View getChangeAnchorView() {
273        return mChangeButton;
274    }
275}
276