Home | History | Annotate | Download | only in phone
      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