Home | History | Annotate | Download | only in calllogutils
      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.dialer.calllogutils;
     18 
     19 import android.content.Context;
     20 import android.content.res.TypedArray;
     21 import android.graphics.Bitmap;
     22 import android.graphics.BitmapFactory;
     23 import android.graphics.Canvas;
     24 import android.graphics.PorterDuff;
     25 import android.graphics.drawable.BitmapDrawable;
     26 import android.graphics.drawable.Drawable;
     27 import android.util.AttributeSet;
     28 import android.view.View;
     29 import com.android.dialer.compat.AppCompatConstants;
     30 import java.util.ArrayList;
     31 import java.util.List;
     32 
     33 /**
     34  * View that draws one or more symbols for different types of calls (missed calls, outgoing etc).
     35  * The symbols are set up horizontally. If {@code useLargeIcons} is set in the xml attributes,
     36  * alternatively this view will only render one icon (Call Type, HD or Video).
     37  *
     38  * <p>As this view doesn't create subviews, it is better suited for ListView-recycling than a
     39  * regular LinearLayout using ImageViews.
     40  */
     41 public class CallTypeIconsView extends View {
     42 
     43   private final boolean useLargeIcons;
     44 
     45   private static Resources sResources;
     46   private static Resources sLargeResouces;
     47   private List<Integer> mCallTypes = new ArrayList<>(3);
     48   private boolean mShowVideo;
     49   private boolean mShowHd;
     50   private boolean mShowWifi;
     51   private int mWidth;
     52   private int mHeight;
     53 
     54   public CallTypeIconsView(Context context) {
     55     this(context, null);
     56   }
     57 
     58   public CallTypeIconsView(Context context, AttributeSet attrs) {
     59     super(context, attrs);
     60     TypedArray typedArray =
     61         context.getTheme().obtainStyledAttributes(attrs, R.styleable.CallTypeIconsView, 0, 0);
     62     useLargeIcons = typedArray.getBoolean(R.styleable.CallTypeIconsView_useLargeIcons, false);
     63     typedArray.recycle();
     64     if (sResources == null) {
     65       sResources = new Resources(context, false);
     66     }
     67     if (sLargeResouces == null && useLargeIcons) {
     68       sLargeResouces = new Resources(context, true);
     69     }
     70   }
     71 
     72   public void clear() {
     73     mCallTypes.clear();
     74     mWidth = 0;
     75     mHeight = 0;
     76     invalidate();
     77   }
     78 
     79   public void add(int callType) {
     80     mCallTypes.add(callType);
     81 
     82     final Drawable drawable = getCallTypeDrawable(callType);
     83     mWidth += drawable.getIntrinsicWidth() + sResources.iconMargin;
     84     mHeight = Math.max(mHeight, drawable.getIntrinsicWidth());
     85     invalidate();
     86   }
     87 
     88   /**
     89    * Determines whether the video call icon will be shown.
     90    *
     91    * @param showVideo True where the video icon should be shown.
     92    */
     93   public void setShowVideo(boolean showVideo) {
     94     mShowVideo = showVideo;
     95     if (showVideo) {
     96       mWidth += sResources.videoCall.getIntrinsicWidth() + sResources.iconMargin;
     97       mHeight = Math.max(mHeight, sResources.videoCall.getIntrinsicHeight());
     98       invalidate();
     99     }
    100   }
    101 
    102   /**
    103    * Determines if the video icon should be shown.
    104    *
    105    * @return True if the video icon should be shown.
    106    */
    107   public boolean isVideoShown() {
    108     return mShowVideo;
    109   }
    110 
    111   public void setShowHd(boolean showHd) {
    112     mShowHd = showHd;
    113     if (showHd) {
    114       mWidth += sResources.hdCall.getIntrinsicWidth() + sResources.iconMargin;
    115       mHeight = Math.max(mHeight, sResources.hdCall.getIntrinsicHeight());
    116       invalidate();
    117     }
    118   }
    119 
    120   public void setShowWifi(boolean showWifi) {
    121     mShowWifi = showWifi;
    122     if (showWifi) {
    123       mWidth += sResources.wifiCall.getIntrinsicWidth() + sResources.iconMargin;
    124       mHeight = Math.max(mHeight, sResources.wifiCall.getIntrinsicHeight());
    125       invalidate();
    126     }
    127   }
    128 
    129   public int getCount() {
    130     return mCallTypes.size();
    131   }
    132 
    133   public int getCallType(int index) {
    134     return mCallTypes.get(index);
    135   }
    136 
    137   private Drawable getCallTypeDrawable(int callType) {
    138     Resources resources = useLargeIcons ? sLargeResouces : sResources;
    139     switch (callType) {
    140       case AppCompatConstants.CALLS_INCOMING_TYPE:
    141       case AppCompatConstants.CALLS_ANSWERED_EXTERNALLY_TYPE:
    142         return resources.incoming;
    143       case AppCompatConstants.CALLS_OUTGOING_TYPE:
    144         return resources.outgoing;
    145       case AppCompatConstants.CALLS_MISSED_TYPE:
    146         return resources.missed;
    147       case AppCompatConstants.CALLS_VOICEMAIL_TYPE:
    148         return resources.voicemail;
    149       case AppCompatConstants.CALLS_BLOCKED_TYPE:
    150         return resources.blocked;
    151       default:
    152         // It is possible for users to end up with calls with unknown call types in their
    153         // call history, possibly due to 3rd party call log implementations (e.g. to
    154         // distinguish between rejected and missed calls). Instead of crashing, just
    155         // assume that all unknown call types are missed calls.
    156         return resources.missed;
    157     }
    158   }
    159 
    160   @Override
    161   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    162     setMeasuredDimension(mWidth, mHeight);
    163   }
    164 
    165   @Override
    166   protected void onDraw(Canvas canvas) {
    167     Resources resources = useLargeIcons ? sLargeResouces : sResources;
    168     int left = 0;
    169     // If we are using large icons, we should only show one icon (video, hd or call type) with
    170     // priority give to HD or Video. So we skip the call type icon if we plan to show them.
    171     if (!useLargeIcons || !(mShowHd || mShowVideo || mShowWifi)) {
    172       for (Integer callType : mCallTypes) {
    173         final Drawable drawable = getCallTypeDrawable(callType);
    174         final int right = left + drawable.getIntrinsicWidth();
    175         drawable.setBounds(left, 0, right, drawable.getIntrinsicHeight());
    176         drawable.draw(canvas);
    177         left = right + resources.iconMargin;
    178       }
    179     }
    180 
    181     // If showing the video call icon, draw it scaled appropriately.
    182     if (mShowVideo) {
    183       left = addDrawable(canvas, resources.videoCall, left) + resources.iconMargin;
    184     }
    185     // If showing HD call icon, draw it scaled appropriately.
    186     if (mShowHd) {
    187       left = addDrawable(canvas, resources.hdCall, left) + resources.iconMargin;
    188     }
    189     // If showing HD call icon, draw it scaled appropriately.
    190     if (mShowWifi) {
    191       left = addDrawable(canvas, resources.wifiCall, left) + resources.iconMargin;
    192     }
    193   }
    194 
    195   private int addDrawable(Canvas canvas, Drawable drawable, int left) {
    196     int right = left + drawable.getIntrinsicWidth();
    197     drawable.setBounds(left, 0, right, drawable.getIntrinsicHeight());
    198     drawable.draw(canvas);
    199     return right;
    200   }
    201 
    202   private static class Resources {
    203 
    204     // Drawable representing an incoming answered call.
    205     public final Drawable incoming;
    206 
    207     // Drawable respresenting an outgoing call.
    208     public final Drawable outgoing;
    209 
    210     // Drawable representing an incoming missed call.
    211     public final Drawable missed;
    212 
    213     // Drawable representing a voicemail.
    214     public final Drawable voicemail;
    215 
    216     // Drawable representing a blocked call.
    217     public final Drawable blocked;
    218 
    219     // Drawable repesenting a video call.
    220     final Drawable videoCall;
    221 
    222     // Drawable represeting a hd call.
    223     final Drawable hdCall;
    224 
    225     // Drawable representing a WiFi call.
    226     final Drawable wifiCall;
    227 
    228     /** The margin to use for icons. */
    229     final int iconMargin;
    230 
    231     /**
    232      * Configures the call icon drawables. A single white call arrow which points down and left is
    233      * used as a basis for all of the call arrow icons, applying rotation and colors as needed.
    234      *
    235      * <p>For each drawable we call mutate so that a new instance of the drawable is created. This
    236      * is done so that when we apply a color filter to the drawables, they are recolored across
    237      * dialer.
    238      *
    239      * @param context The current context.
    240      */
    241     public Resources(Context context, boolean largeIcons) {
    242       final android.content.res.Resources r = context.getResources();
    243 
    244       int iconId = R.drawable.quantum_ic_call_received_white_24;
    245       Drawable drawable = largeIcons ? r.getDrawable(iconId) : getScaledBitmap(context, iconId);
    246       incoming = drawable.mutate();
    247       incoming.setColorFilter(r.getColor(R.color.answered_call), PorterDuff.Mode.MULTIPLY);
    248 
    249       // Create a rotated instance of the call arrow for outgoing calls.
    250       iconId = R.drawable.quantum_ic_call_made_white_24;
    251       drawable = largeIcons ? r.getDrawable(iconId) : getScaledBitmap(context, iconId);
    252       outgoing = drawable.mutate();
    253       outgoing.setColorFilter(r.getColor(R.color.answered_call), PorterDuff.Mode.MULTIPLY);
    254 
    255       // Need to make a copy of the arrow drawable, otherwise the same instance colored
    256       // above will be recolored here.
    257       iconId = R.drawable.quantum_ic_call_missed_white_24;
    258       drawable = largeIcons ? r.getDrawable(iconId) : getScaledBitmap(context, iconId);
    259       missed = drawable.mutate();
    260       missed.setColorFilter(r.getColor(R.color.missed_call), PorterDuff.Mode.MULTIPLY);
    261 
    262       iconId = R.drawable.quantum_ic_voicemail_white_24;
    263       drawable = largeIcons ? r.getDrawable(iconId) : getScaledBitmap(context, iconId);
    264       voicemail = drawable.mutate();
    265       voicemail.setColorFilter(r.getColor(R.color.call_type_icon_color), PorterDuff.Mode.MULTIPLY);
    266 
    267       iconId = R.drawable.quantum_ic_block_white_24;
    268       drawable = largeIcons ? r.getDrawable(iconId) : getScaledBitmap(context, iconId);
    269       blocked = drawable.mutate();
    270       blocked.setColorFilter(r.getColor(R.color.blocked_call), PorterDuff.Mode.MULTIPLY);
    271 
    272       iconId = R.drawable.quantum_ic_videocam_white_24;
    273       drawable = largeIcons ? r.getDrawable(iconId) : getScaledBitmap(context, iconId);
    274       videoCall = drawable.mutate();
    275       videoCall.setColorFilter(r.getColor(R.color.call_type_icon_color), PorterDuff.Mode.MULTIPLY);
    276 
    277       iconId = R.drawable.quantum_ic_hd_white_24;
    278       drawable = largeIcons ? r.getDrawable(iconId) : getScaledBitmap(context, iconId);
    279       hdCall = drawable.mutate();
    280       hdCall.setColorFilter(r.getColor(R.color.call_type_icon_color), PorterDuff.Mode.MULTIPLY);
    281 
    282       iconId = R.drawable.quantum_ic_signal_wifi_4_bar_white_24;
    283       drawable = largeIcons ? r.getDrawable(iconId) : getScaledBitmap(context, iconId);
    284       wifiCall = drawable.mutate();
    285       wifiCall.setColorFilter(r.getColor(R.color.call_type_icon_color), PorterDuff.Mode.MULTIPLY);
    286 
    287       iconMargin = largeIcons ? 0 : r.getDimensionPixelSize(R.dimen.call_log_icon_margin);
    288     }
    289 
    290     // Gets the icon, scaled to the height of the call type icons. This helps display all the
    291     // icons to be the same height, while preserving their width aspect ratio.
    292     private Drawable getScaledBitmap(Context context, int resourceId) {
    293       Bitmap icon = BitmapFactory.decodeResource(context.getResources(), resourceId);
    294       int scaledHeight = context.getResources().getDimensionPixelSize(R.dimen.call_type_icon_size);
    295       int scaledWidth =
    296           (int) ((float) icon.getWidth() * ((float) scaledHeight / (float) icon.getHeight()));
    297       Bitmap scaledIcon = Bitmap.createScaledBitmap(icon, scaledWidth, scaledHeight, false);
    298       return new BitmapDrawable(context.getResources(), scaledIcon);
    299     }
    300   }
    301 }
    302