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