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 com.android.contacts.R; 20 import com.android.contacts.common.ContactPhotoManager; 21 import com.android.contacts.common.model.ValuesDelta; 22 import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette; 23 import com.android.contacts.util.SchedulingUtils; 24 import com.android.contacts.widget.QuickContactImageView; 25 26 import android.app.Activity; 27 import android.content.Context; 28 import android.content.res.TypedArray; 29 import android.graphics.Bitmap; 30 import android.net.Uri; 31 import android.provider.ContactsContract; 32 import android.util.AttributeSet; 33 import android.util.DisplayMetrics; 34 import android.util.TypedValue; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.widget.RelativeLayout; 38 39 /** 40 * Displays a photo and calls the host back when the user clicks it. 41 */ 42 public 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 */ onPhotoEditorViewClicked()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 CompactPhotoEditorView(Context context)72 public CompactPhotoEditorView(Context context) { 73 this(context, null); 74 } 75 CompactPhotoEditorView(Context context, AttributeSet attrs)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.quickcontact_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 getTypedFloat(int resourceId)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 onFinishInflate()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 setListener(Listener listener)108 public void setListener(Listener listener) { 109 mListener = listener; 110 } 111 setReadOnly(boolean readOnly)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 */ setPhoto(ValuesDelta valuesDelta, MaterialPalette materialPalette)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 adjustDimensions()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 getContentViewWidth()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 getContentViewHeight()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 */ isWritablePhotoSet()193 public boolean isWritablePhotoSet() { 194 return !mReadOnly && mIsNonDefaultPhotoBound; 195 } 196 197 /** 198 * Binds the given bitmap. 199 */ setPhoto(Bitmap bitmap)200 private void setPhoto(Bitmap bitmap) { 201 mPhotoImageView.setImageBitmap(bitmap); 202 mIsNonDefaultPhotoBound = true; 203 } 204 setDefaultPhoto(MaterialPalette materialPalette)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 */ setFullSizedPhoto(Uri photoUri)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 */ removePhoto()221 public void removePhoto() { 222 mPhotoImageView.setImageBitmap(/* bitmap =*/ null); 223 mIsNonDefaultPhotoBound = false; 224 setDefaultPhoto(/* materialPalette =*/ null); 225 } 226 227 @Override onClick(View view)228 public void onClick(View view) { 229 if (mListener != null) { 230 mListener.onPhotoEditorViewClicked(); 231 } 232 } 233 } 234