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