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