• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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