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