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