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