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