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 
PhotoEditorView(Context context)57     public PhotoEditorView(Context context) {
58         super(context);
59     }
60 
PhotoEditorView(Context context, AttributeSet attrs)61     public PhotoEditorView(Context context, AttributeSet attrs) {
62         super(context, attrs);
63     }
64 
65     @Override
setEnabled(boolean enabled)66     public void setEnabled(boolean enabled) {
67         super.setEnabled(enabled);
68     }
69 
70     @Override
editNewlyAddedField()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
onFinishInflate()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
onFieldChanged(String column, String value)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
setValues(DataKind kind, ValuesDelta values, RawContactDelta state, boolean readOnly, ViewIdGenerator vig)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      */
setShowPrimary(boolean showPrimaryCheckBox)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      */
hasSetPhoto()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      */
setPhotoEntry(Bitmap photo)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      */
setFullSizedPhoto(Uri photoUri)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      */
setSuperPrimary(boolean superPrimary)226     public void setSuperPrimary(boolean superPrimary) {
227         mEntry.put(Photo.IS_SUPER_PRIMARY, superPrimary ? 1 : 0);
228     }
229 
resetDefault()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
setEditorListener(EditorListener listener)240     public void setEditorListener(EditorListener listener) {
241         mListener = listener;
242     }
243 
244     @Override
setDeletable(boolean deletable)245     public void setDeletable(boolean deletable) {
246         // Photo is not deletable
247     }
248 
249     @Override
isEmpty()250     public boolean isEmpty() {
251         return !mHasSetPhoto;
252     }
253 
254     @Override
markDeleted()255     public void markDeleted() {
256         // Photo is not deletable
257     }
258 
259     @Override
deleteEditor()260     public void deleteEditor() {
261         // Photo is not deletable
262     }
263 
264     @Override
clearAllFields()265     public void clearAllFields() {
266         resetDefault();
267     }
268 
269     /**
270      * The change drop down menu should be anchored to this view.
271      */
getChangeAnchorView()272     public View getChangeAnchorView() {
273         return mChangeButton;
274     }
275 }
276