/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.phone; import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.SystemClock; import android.os.SystemProperties; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.widget.ImageView; /** * ImageView subclass used for contact photos in the in-call UI. * * For contact photos larger than 96x96, this view behaves just like a regular * ImageView. But for 96x96 or smaller (i.e. the size of contact thumbnails * we typically get from contacts sync), apply a "blur + inset" special effect * rather than simply scaling up the image. (Scaling looks terrible because * the onscreen ImageView is so much larger than the source image.) * * Watch out: this widget only does the "blur + inset" effect in one very * specific case: you must set the photo using the setImageDrawable() API, * *and* pass in a drawable that's an instance of BitmapDrawable. * (This is exactly what the in-call UI does; see CallCard.java and also * android.pim.ContactsAsyncHelper.) * * TODO: If we ever intend to expose this class for more general use (or move * it into the framework) we'll need to make this effect work for all the * various setImage*() calls, with any kind of drawable. * * TODO: other features to consider adding here: * - any special scaling / cropping behavior? * - special handling for the "unknown" contact photo and the "conference call" state? * - allow the whole image to be blurred or dimmed, regardless of the * size of the input image (like for a call that's on hold) */ public class InCallContactPhoto extends ImageView { private static final String TAG = "InCallContactPhoto"; private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1); private static final boolean VDBG = false; /** * If true, enable the "blur + inset" special effect for lo-res * images. (This flag provides a quick way to disable this class * entirely; if false, InCallContactPhoto instances will behave just * like plain old ImageViews.) */ private static final boolean ENABLE_BLUR_INSET_EFFECT = false; private Drawable mPreviousImageDrawable; private ImageView mInsetImageView; public InCallContactPhoto(Context context) { super(context); } public InCallContactPhoto(Context context, AttributeSet attrs) { super(context, attrs); } public InCallContactPhoto(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public void setInsetImageView(ImageView imageView) { mInsetImageView = imageView; } @Override public void setImageResource(int resId) { if (DBG) log("setImageResource(" + resId + ")..."); // For now, at least, this method doesn't trigger any special effects // (see the TODO comment in the class javadoc.) mPreviousImageDrawable = null; hideInset(); super.setImageResource(resId); } @Override public void setImageURI(Uri uri) { if (DBG) log("setImageURI(" + uri + ")..."); // For now, at least, this method doesn't trigger any special effects // (see the TODO comment in the class javadoc.) mPreviousImageDrawable = null; hideInset(); super.setImageURI(uri); } @Override public void setImageBitmap(Bitmap bm) { if (DBG) log("setImageBitmap(" + bm + ")..."); // For now, at least, this method doesn't trigger any special effects // (see the TODO comment in the class javadoc.) mPreviousImageDrawable = null; hideInset(); super.setImageBitmap(bm); } @Override public void setImageDrawable(Drawable inputDrawable) { if (DBG) log("setImageDrawable(" + inputDrawable + ")..."); long startTime = SystemClock.uptimeMillis(); BitmapDrawable blurredBitmapDrawable = null; if (VDBG) log("################# setImageDrawable()... ################"); if (VDBG) log("- this: " + this); if (VDBG) log("- inputDrawable: " + inputDrawable); if (VDBG) log("- mPreviousImageDrawable: " + mPreviousImageDrawable); if (inputDrawable != mPreviousImageDrawable) { mPreviousImageDrawable = inputDrawable; if (inputDrawable instanceof BitmapDrawable) { Bitmap inputBitmap = ((BitmapDrawable) inputDrawable).getBitmap(); if (VDBG) log("- inputBitmap: " + inputBitmap); if (VDBG) log(" - dimensions: " + inputBitmap.getWidth() + " x " + inputBitmap.getHeight()); if (VDBG) log(" - config: " + inputBitmap.getConfig()); if (VDBG) log(" - byte count: " + inputBitmap.getByteCount()); if (!ENABLE_BLUR_INSET_EFFECT) { if (DBG) log("- blur+inset disabled; no special effect."); // ...and leave blurredBitmapDrawable = null so that we'll // fall back to the regular ImageView behavior (see below.) } else if (inputBitmap == null) { Log.w(TAG, "setImageDrawable: null bitmap from inputDrawable.getBitmap()!"); // ...and leave blurredBitmapDrawable = null so that we'll // fall back to the regular ImageView behavior (see below.) } else if (!isLoRes(inputBitmap)) { if (DBG) log("- not a lo-res bitmap; no special effect."); // ...and leave blurredBitmapDrawable = null so that we'll // fall back to the regular ImageView behavior (see below.) } else { // Ok, we have a valid bitmap *and* it's lo-res. // Do the blur + inset effect. if (DBG) log("- got a lo-res bitmap; blurring..."); Bitmap blurredBitmap = BitmapUtils.createBlurredBitmap(inputBitmap); if (VDBG) log("- blurredBitmap: " + blurredBitmap); if (VDBG) log(" - dimensions: " + blurredBitmap.getWidth() + " x " + blurredBitmap.getHeight()); if (VDBG) log(" - config: " + blurredBitmap.getConfig()); if (VDBG) log(" - byte count: " + blurredBitmap.getByteCount()); blurredBitmapDrawable = new BitmapDrawable(getResources(), blurredBitmap); if (DBG) log("- Created blurredBitmapDrawable: " + blurredBitmapDrawable); } } else { Log.w(TAG, "setImageDrawable: inputDrawable '" + inputDrawable + "' is not a BitmapDrawable"); // For now, at least, we don't trigger any special effects in // this case (see the TODO comment in the class javadoc.) // Just leave blurredBitmapDrawable = null so that we'll // fall back to the regular ImageView behavior (see below.) } if (blurredBitmapDrawable != null) { if (DBG) log("- Show the special effect! blurredBitmapDrawable = " + blurredBitmapDrawable); super.setImageDrawable(blurredBitmapDrawable); // And show the original (sharp) image in the inset. showInset(inputDrawable); } else { if (DBG) log("- null blurredBitmapDrawable; don't show the special effect."); // Otherwise, Just fall back to the regular ImageView behavior. super.setImageDrawable(inputDrawable); hideInset(); } } long endTime = SystemClock.uptimeMillis(); if (DBG) log("setImageDrawable() done: *ELAPSED* = " + (endTime - startTime) + " msec"); } /** * @return true if the specified bitmap is a lo-res contact photo * (i.e. if we *should* use the blur+inset effect for this photo * in the in-call UI.) */ private boolean isLoRes(Bitmap bitmap) { // In practice, contact photos will almost always be either 96x96 (for // thumbnails from contacts sync) or 256x256 (if you pick a photo from // the gallery or camera via the contacts app.) // // So enable the blur+inset effect *only* for width = 96 or smaller. // (If the user somehow gets a contact to have a photo that's between // 97 and 255 pixels wide, that's OK, we'll just show it as-is with no // special effects.) final int LO_RES_THRESHOLD_WIDTH = 96; if (DBG) log("- isLoRes: checking bitmap with width " + bitmap.getWidth() + "..."); return (bitmap.getWidth() <= LO_RES_THRESHOLD_WIDTH); } private void hideInset() { if (DBG) log("- hideInset()..."); if (mInsetImageView != null) { mInsetImageView.setVisibility(View.GONE); } } private void showInset(Drawable drawable) { if (DBG) log("- showInset(Drawable " + drawable + ")..."); if (mInsetImageView != null) { mInsetImageView.setImageDrawable(drawable); mInsetImageView.setVisibility(View.VISIBLE); } } private void log(String msg) { Log.d(TAG, msg); } }