/*
* 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);
}
}