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 android.content.Context;
20 import android.graphics.Bitmap;
21 import android.net.Uri;
22 import android.provider.ContactsContract;
23 import android.util.AttributeSet;
24 import android.util.TypedValue;
25 import android.view.View;
26 import android.view.ViewGroup;
27 import android.widget.RelativeLayout;
28 
29 import com.android.contacts.ContactPhotoManager;
30 import com.android.contacts.R;
31 import com.android.contacts.model.ValuesDelta;
32 import com.android.contacts.util.MaterialColorMapUtils.MaterialPalette;
33 import com.android.contacts.util.SchedulingUtils;
34 import com.android.contacts.widget.QuickContactImageView;
35 
36 /**
37  * Displays a photo and calls the host back when the user clicks it.
38  */
39 public class PhotoEditorView extends RelativeLayout implements View.OnClickListener {
40 
41     /**
42      * Callbacks for the host of this view.
43      */
44     public interface Listener {
45 
46         /**
47          * Invoked when the user wants to change their photo.
48          */
onPhotoEditorViewClicked()49         void onPhotoEditorViewClicked();
50     }
51 
52     private Listener mListener;
53 
54     private final float mLandscapePhotoRatio;
55     private final float mPortraitPhotoRatio;
56     private final boolean mIsTwoPanel;
57 
58     private QuickContactImageView mPhotoImageView;
59     private View mPhotoIcon;
60     private View mPhotoIconOverlay;
61     private View mPhotoTouchInterceptOverlay;
62     private MaterialPalette mMaterialPalette;
63 
64     private boolean mReadOnly;
65     private boolean mIsNonDefaultPhotoBound;
66 
PhotoEditorView(Context context)67     public PhotoEditorView(Context context) {
68         this(context, null);
69     }
70 
PhotoEditorView(Context context, AttributeSet attrs)71     public PhotoEditorView(Context context, AttributeSet attrs) {
72         super(context, attrs);
73 
74         mLandscapePhotoRatio = getTypedFloat(R.dimen.quickcontact_landscape_photo_ratio);
75         mPortraitPhotoRatio = getTypedFloat(R.dimen.editor_portrait_photo_ratio);
76         mIsTwoPanel = getResources().getBoolean(R.bool.contacteditor_two_panel);
77     }
78 
getTypedFloat(int resourceId)79     private float getTypedFloat(int resourceId) {
80         final TypedValue typedValue = new TypedValue();
81         getResources().getValue(resourceId, typedValue, /* resolveRefs =*/ true);
82         return typedValue.getFloat();
83     }
84 
85     @Override
onFinishInflate()86     protected void onFinishInflate() {
87         super.onFinishInflate();
88         mPhotoImageView = (QuickContactImageView) findViewById(R.id.photo);
89         mPhotoIcon = findViewById(R.id.photo_icon);
90         mPhotoIconOverlay = findViewById(R.id.photo_icon_overlay);
91         mPhotoTouchInterceptOverlay = findViewById(R.id.photo_touch_intercept_overlay);
92 
93     }
94 
setListener(Listener listener)95     public void setListener(Listener listener) {
96         mListener = listener;
97     }
98 
setReadOnly(boolean readOnly)99     public void setReadOnly(boolean readOnly) {
100         mReadOnly = readOnly;
101         if (mReadOnly) {
102             mPhotoIcon.setVisibility(View.GONE);
103             mPhotoIconOverlay.setVisibility(View.GONE);
104             mPhotoTouchInterceptOverlay.setClickable(false);
105             mPhotoTouchInterceptOverlay.setContentDescription(getContext().getString(
106                     R.string.editor_contact_photo_content_description));
107         } else {
108             mPhotoIcon.setVisibility(View.VISIBLE);
109             mPhotoIconOverlay.setVisibility(View.VISIBLE);
110             mPhotoTouchInterceptOverlay.setOnClickListener(this);
111             updatePhotoDescription();
112         }
113     }
114 
setPalette(MaterialPalette palette)115     public void setPalette(MaterialPalette palette) {
116         mMaterialPalette = palette;
117     }
118 
119     /**
120      * Tries to bind a full size photo or a bitmap loaded from the given ValuesDelta,
121      * and falls back to the default avatar, tinted using the given MaterialPalette (if it's not
122      * null);
123      */
setPhoto(ValuesDelta valuesDelta)124     public void setPhoto(ValuesDelta valuesDelta) {
125         // Check if we can update to the full size photo immediately
126         final Long photoFileId = EditorUiUtils.getPhotoFileId(valuesDelta);
127         if (photoFileId != null) {
128             final Uri photoUri = ContactsContract.DisplayPhoto.CONTENT_URI.buildUpon()
129                     .appendPath(photoFileId.toString()).build();
130             setFullSizedPhoto(photoUri);
131             adjustDimensions();
132             return;
133         }
134 
135         // Use the bitmap image from the values delta
136         final Bitmap bitmap = EditorUiUtils.getPhotoBitmap(valuesDelta);
137         if (bitmap != null) {
138             setPhoto(bitmap);
139             adjustDimensions();
140             return;
141         }
142 
143         setDefaultPhoto(mMaterialPalette);
144         adjustDimensions();
145     }
146 
adjustDimensions()147     private void adjustDimensions() {
148         // Follow the same logic as MultiShrinkScroll.initialize
149         SchedulingUtils.doOnPreDraw(this, /* drawNextFrame =*/ false, new Runnable() {
150             @Override
151             public void run() {
152                 final int photoHeight, photoWidth;
153                 if (mIsTwoPanel) {
154                     photoHeight = getHeight();
155                     photoWidth = (int) (photoHeight * mLandscapePhotoRatio);
156                 } else {
157                     // Make the photo slightly shorter that it is wide
158                     photoWidth = getWidth();
159                     photoHeight = (int) (photoWidth / mPortraitPhotoRatio);
160                 }
161                 final ViewGroup.LayoutParams layoutParams = getLayoutParams();
162                 layoutParams.height = photoHeight;
163                 layoutParams.width = photoWidth;
164                 setLayoutParams(layoutParams);
165             }
166         });
167     }
168 
169     /**
170      * Whether a removable, non-default photo is bound to this view.
171      */
isWritablePhotoSet()172     public boolean isWritablePhotoSet() {
173         return !mReadOnly && mIsNonDefaultPhotoBound;
174     }
175 
176     /**
177      * Binds the given bitmap.
178      */
setPhoto(Bitmap bitmap)179     private void setPhoto(Bitmap bitmap) {
180         mPhotoImageView.setImageBitmap(bitmap);
181         mIsNonDefaultPhotoBound = true;
182         updatePhotoDescription();
183     }
184 
setDefaultPhoto(MaterialPalette materialPalette)185     private void setDefaultPhoto(MaterialPalette materialPalette) {
186         mIsNonDefaultPhotoBound = false;
187         updatePhotoDescription();
188         EditorUiUtils.setDefaultPhoto(mPhotoImageView, getResources(), materialPalette);
189     }
190 
updatePhotoDescription()191     private void updatePhotoDescription() {
192         mPhotoTouchInterceptOverlay.setContentDescription(getContext().getString(
193                 mIsNonDefaultPhotoBound
194                         ? R.string.editor_change_photo_content_description
195                         : R.string.editor_add_photo_content_description));
196     }
197     /**
198      * Binds a full size photo loaded from the given Uri.
199      */
setFullSizedPhoto(Uri photoUri)200     public void setFullSizedPhoto(Uri photoUri) {
201         mPhotoImageView.setImageURI(photoUri);
202         mIsNonDefaultPhotoBound = true;
203         updatePhotoDescription();
204     }
205 
206     /**
207      * Removes the current bound photo bitmap.
208      */
removePhoto()209     public void removePhoto() {
210         setDefaultPhoto(mMaterialPalette);
211     }
212 
213     @Override
onClick(View view)214     public void onClick(View view) {
215         if (mListener != null) {
216             mListener.onPhotoEditorViewClicked();
217         }
218     }
219 }
220