1 /* 2 * Copyright (C) 2011 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.phone; 18 19 import android.content.Context; 20 import android.graphics.Bitmap; 21 import android.graphics.drawable.BitmapDrawable; 22 import android.graphics.drawable.Drawable; 23 import android.net.Uri; 24 import android.os.SystemClock; 25 import android.os.SystemProperties; 26 import android.util.AttributeSet; 27 import android.util.Log; 28 import android.view.View; 29 import android.widget.ImageView; 30 31 32 /** 33 * ImageView subclass used for contact photos in the in-call UI. 34 * 35 * For contact photos larger than 96x96, this view behaves just like a regular 36 * ImageView. But for 96x96 or smaller (i.e. the size of contact thumbnails 37 * we typically get from contacts sync), apply a "blur + inset" special effect 38 * rather than simply scaling up the image. (Scaling looks terrible because 39 * the onscreen ImageView is so much larger than the source image.) 40 * 41 * Watch out: this widget only does the "blur + inset" effect in one very 42 * specific case: you must set the photo using the setImageDrawable() API, 43 * *and* pass in a drawable that's an instance of BitmapDrawable. 44 * (This is exactly what the in-call UI does; see CallCard.java and also 45 * android.pim.ContactsAsyncHelper.) 46 * 47 * TODO: If we ever intend to expose this class for more general use (or move 48 * it into the framework) we'll need to make this effect work for all the 49 * various setImage*() calls, with any kind of drawable. 50 * 51 * TODO: other features to consider adding here: 52 * - any special scaling / cropping behavior? 53 * - special handling for the "unknown" contact photo and the "conference 54 call" state? 55 * - allow the whole image to be blurred or dimmed, regardless of the 56 * size of the input image (like for a call that's on hold) 57 */ 58 public class InCallContactPhoto extends ImageView { 59 private static final String TAG = "InCallContactPhoto"; 60 private static final boolean DBG = 61 (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1); 62 private static final boolean VDBG = false; 63 64 /** 65 * If true, enable the "blur + inset" special effect for lo-res 66 * images. (This flag provides a quick way to disable this class 67 * entirely; if false, InCallContactPhoto instances will behave just 68 * like plain old ImageViews.) 69 */ 70 private static final boolean ENABLE_BLUR_INSET_EFFECT = false; 71 72 private Drawable mPreviousImageDrawable; 73 private ImageView mInsetImageView; 74 75 public InCallContactPhoto(Context context) { 76 super(context); 77 } 78 79 public InCallContactPhoto(Context context, AttributeSet attrs) { 80 super(context, attrs); 81 } 82 83 public InCallContactPhoto(Context context, AttributeSet attrs, int defStyle) { 84 super(context, attrs, defStyle); 85 } 86 87 public void setInsetImageView(ImageView imageView) { 88 mInsetImageView = imageView; 89 } 90 91 @Override 92 public void setImageResource(int resId) { 93 if (DBG) log("setImageResource(" + resId + ")..."); 94 // For now, at least, this method doesn't trigger any special effects 95 // (see the TODO comment in the class javadoc.) 96 mPreviousImageDrawable = null; 97 hideInset(); 98 super.setImageResource(resId); 99 } 100 101 @Override 102 public void setImageURI(Uri uri) { 103 if (DBG) log("setImageURI(" + uri + ")..."); 104 // For now, at least, this method doesn't trigger any special effects 105 // (see the TODO comment in the class javadoc.) 106 mPreviousImageDrawable = null; 107 hideInset(); 108 super.setImageURI(uri); 109 } 110 111 @Override 112 public void setImageBitmap(Bitmap bm) { 113 if (DBG) log("setImageBitmap(" + bm + ")..."); 114 // For now, at least, this method doesn't trigger any special effects 115 // (see the TODO comment in the class javadoc.) 116 mPreviousImageDrawable = null; 117 hideInset(); 118 super.setImageBitmap(bm); 119 } 120 121 @Override 122 public void setImageDrawable(Drawable inputDrawable) { 123 if (DBG) log("setImageDrawable(" + inputDrawable + ")..."); 124 long startTime = SystemClock.uptimeMillis(); 125 126 BitmapDrawable blurredBitmapDrawable = null; 127 128 if (VDBG) log("################# setImageDrawable()... ################"); 129 if (VDBG) log("- this: " + this); 130 if (VDBG) log("- inputDrawable: " + inputDrawable); 131 if (VDBG) log("- mPreviousImageDrawable: " + mPreviousImageDrawable); 132 133 if (inputDrawable != mPreviousImageDrawable) { 134 135 mPreviousImageDrawable = inputDrawable; 136 137 if (inputDrawable instanceof BitmapDrawable) { 138 Bitmap inputBitmap = ((BitmapDrawable) inputDrawable).getBitmap(); 139 140 if (VDBG) log("- inputBitmap: " + inputBitmap); 141 if (VDBG) log(" - dimensions: " + inputBitmap.getWidth() 142 + " x " + inputBitmap.getHeight()); 143 if (VDBG) log(" - config: " + inputBitmap.getConfig()); 144 if (VDBG) log(" - byte count: " + inputBitmap.getByteCount()); 145 146 if (!ENABLE_BLUR_INSET_EFFECT) { 147 if (DBG) log("- blur+inset disabled; no special effect."); 148 // ...and leave blurredBitmapDrawable = null so that we'll 149 // fall back to the regular ImageView behavior (see below.) 150 } else if (inputBitmap == null) { 151 Log.w(TAG, "setImageDrawable: null bitmap from inputDrawable.getBitmap()!"); 152 // ...and leave blurredBitmapDrawable = null so that we'll 153 // fall back to the regular ImageView behavior (see below.) 154 } else if (!isLoRes(inputBitmap)) { 155 if (DBG) log("- not a lo-res bitmap; no special effect."); 156 // ...and leave blurredBitmapDrawable = null so that we'll 157 // fall back to the regular ImageView behavior (see below.) 158 } else { 159 // Ok, we have a valid bitmap *and* it's lo-res. 160 // Do the blur + inset effect. 161 if (DBG) log("- got a lo-res bitmap; blurring..."); 162 Bitmap blurredBitmap = BitmapUtils.createBlurredBitmap(inputBitmap); 163 if (VDBG) log("- blurredBitmap: " + blurredBitmap); 164 if (VDBG) log(" - dimensions: " + blurredBitmap.getWidth() 165 + " x " + blurredBitmap.getHeight()); 166 if (VDBG) log(" - config: " + blurredBitmap.getConfig()); 167 if (VDBG) log(" - byte count: " + blurredBitmap.getByteCount()); 168 169 blurredBitmapDrawable = new BitmapDrawable(getResources(), blurredBitmap); 170 if (DBG) log("- Created blurredBitmapDrawable: " + blurredBitmapDrawable); 171 } 172 } else { 173 Log.w(TAG, "setImageDrawable: inputDrawable '" + inputDrawable 174 + "' is not a BitmapDrawable"); 175 // For now, at least, we don't trigger any special effects in 176 // this case (see the TODO comment in the class javadoc.) 177 // Just leave blurredBitmapDrawable = null so that we'll 178 // fall back to the regular ImageView behavior (see below.) 179 } 180 181 if (blurredBitmapDrawable != null) { 182 if (DBG) log("- Show the special effect! blurredBitmapDrawable = " 183 + blurredBitmapDrawable); 184 super.setImageDrawable(blurredBitmapDrawable); 185 // And show the original (sharp) image in the inset. 186 showInset(inputDrawable); 187 } else { 188 if (DBG) log("- null blurredBitmapDrawable; don't show the special effect."); 189 // Otherwise, Just fall back to the regular ImageView behavior. 190 super.setImageDrawable(inputDrawable); 191 hideInset(); 192 } 193 } 194 195 long endTime = SystemClock.uptimeMillis(); 196 if (DBG) log("setImageDrawable() done: *ELAPSED* = " + (endTime - startTime) + " msec"); 197 } 198 199 /** 200 * @return true if the specified bitmap is a lo-res contact photo 201 * (i.e. if we *should* use the blur+inset effect for this photo 202 * in the in-call UI.) 203 */ 204 private boolean isLoRes(Bitmap bitmap) { 205 // In practice, contact photos will almost always be either 96x96 (for 206 // thumbnails from contacts sync) or 256x256 (if you pick a photo from 207 // the gallery or camera via the contacts app.) 208 // 209 // So enable the blur+inset effect *only* for width = 96 or smaller. 210 // (If the user somehow gets a contact to have a photo that's between 211 // 97 and 255 pixels wide, that's OK, we'll just show it as-is with no 212 // special effects.) 213 final int LO_RES_THRESHOLD_WIDTH = 96; 214 if (DBG) log("- isLoRes: checking bitmap with width " + bitmap.getWidth() + "..."); 215 return (bitmap.getWidth() <= LO_RES_THRESHOLD_WIDTH); 216 } 217 218 private void hideInset() { 219 if (DBG) log("- hideInset()..."); 220 if (mInsetImageView != null) { 221 mInsetImageView.setVisibility(View.GONE); 222 } 223 } 224 225 private void showInset(Drawable drawable) { 226 if (DBG) log("- showInset(Drawable " + drawable + ")..."); 227 if (mInsetImageView != null) { 228 mInsetImageView.setImageDrawable(drawable); 229 mInsetImageView.setVisibility(View.VISIBLE); 230 } 231 } 232 233 private void log(String msg) { 234 Log.d(TAG, msg); 235 } 236 } 237