Replace the list of call symbols by a single custom view to paint them.

Saves about 7-21 views per screen and should be more efficient to recycle

Bug:5099652
Change-Id: I10a1b1d188c5c58329de4ba063d41f8c116c3497
diff --git a/src/com/android/contacts/CallDetailActivity.java b/src/com/android/contacts/CallDetailActivity.java
index a46d2e2..614613c 100644
--- a/src/com/android/contacts/CallDetailActivity.java
+++ b/src/com/android/contacts/CallDetailActivity.java
@@ -148,7 +148,7 @@
         mResources = getResources();
 
         mPhoneCallDetailsViews = PhoneCallDetailsViews.fromView(getWindow().getDecorView());
-        mCallTypeHelper = new CallTypeHelper(getResources(), mInflater);
+        mCallTypeHelper = new CallTypeHelper(getResources());
         mPhoneNumberHelper = new PhoneNumberHelper(mResources, getVoicemailNumber());
         mPhoneCallDetailsHelper = new PhoneCallDetailsHelper(mResources, mCallTypeHelper,
                 mPhoneNumberHelper);
diff --git a/src/com/android/contacts/PhoneCallDetailsHelper.java b/src/com/android/contacts/PhoneCallDetailsHelper.java
index f312a5d..8cdd0d0 100644
--- a/src/com/android/contacts/PhoneCallDetailsHelper.java
+++ b/src/com/android/contacts/PhoneCallDetailsHelper.java
@@ -61,10 +61,10 @@
     public void setPhoneCallDetails(PhoneCallDetailsViews views, PhoneCallDetails details,
             boolean useIcons, boolean isHighlighted, boolean nameOnly) {
         if (useIcons) {
-            views.callTypeIcons.removeAllViews();
+            views.callTypeIcons.clear();
             int count = details.callTypes.length;
             for (int index = 0; index < count && index < MAX_CALL_TYPE_ICONS; ++index) {
-                mCallTypeHelper.inflateCallTypeIcon(details.callTypes[index], views.callTypeIcons);
+                views.callTypeIcons.add(details.callTypes[index]);
             }
             views.callTypeIcons.setVisibility(View.VISIBLE);
             if (count > MAX_CALL_TYPE_ICONS) {
@@ -77,14 +77,13 @@
                 views.callTypeSeparator.setVisibility(View.GONE);
             }
         } else {
-            String callTypeName;
             // Use the name of the first call type.
             // TODO: We should update this to handle the text for multiple calls as well.
             int callType = details.callTypes[0];
             views.callTypeText.setText(
                     isHighlighted ? mCallTypeHelper.getHighlightedCallTypeText(callType)
                             : mCallTypeHelper.getCallTypeText(callType));
-            views.callTypeIcons.removeAllViews();
+            views.callTypeIcons.clear();
 
             views.callTypeText.setVisibility(View.VISIBLE);
             views.callTypeSeparator.setVisibility(View.VISIBLE);
diff --git a/src/com/android/contacts/PhoneCallDetailsViews.java b/src/com/android/contacts/PhoneCallDetailsViews.java
index 19e931f..c07e337 100644
--- a/src/com/android/contacts/PhoneCallDetailsViews.java
+++ b/src/com/android/contacts/PhoneCallDetailsViews.java
@@ -16,9 +16,10 @@
 
 package com.android.contacts;
 
+import com.android.contacts.calllog.CallTypeIconsView;
+
 import android.content.Context;
 import android.view.View;
-import android.widget.LinearLayout;
 import android.widget.TextView;
 
 /**
@@ -27,14 +28,15 @@
 public final class PhoneCallDetailsViews {
     public final TextView nameView;
     public final View callTypeView;
-    public final LinearLayout callTypeIcons;
+    public final CallTypeIconsView callTypeIcons;
     public final TextView callTypeText;
     public final View callTypeSeparator;
     public final TextView dateView;
     public final TextView numberView;
 
-    private PhoneCallDetailsViews(TextView nameView, View callTypeView, LinearLayout callTypeIcons,
-            TextView callTypeText, View callTypeSeparator, TextView dateView, TextView numberView) {
+    private PhoneCallDetailsViews(TextView nameView, View callTypeView,
+            CallTypeIconsView callTypeIcons, TextView callTypeText, View callTypeSeparator,
+            TextView dateView, TextView numberView) {
         this.nameView = nameView;
         this.callTypeView = callTypeView;
         this.callTypeIcons = callTypeIcons;
@@ -54,7 +56,7 @@
     public static PhoneCallDetailsViews fromView(View view) {
         return new PhoneCallDetailsViews((TextView) view.findViewById(R.id.name),
                 view.findViewById(R.id.call_type),
-                (LinearLayout) view.findViewById(R.id.call_type_icons),
+                (CallTypeIconsView) view.findViewById(R.id.call_type_icons),
                 (TextView) view.findViewById(R.id.call_type_name),
                 view.findViewById(R.id.call_type_separator),
                 (TextView) view.findViewById(R.id.date),
@@ -65,7 +67,7 @@
         return new PhoneCallDetailsViews(
                 new TextView(context),
                 new View(context),
-                new LinearLayout(context),
+                new CallTypeIconsView(context),
                 new TextView(context),
                 new View(context),
                 new TextView(context),
diff --git a/src/com/android/contacts/calllog/CallDetailHistoryAdapter.java b/src/com/android/contacts/calllog/CallDetailHistoryAdapter.java
index 26275af..3e3ba36 100644
--- a/src/com/android/contacts/calllog/CallDetailHistoryAdapter.java
+++ b/src/com/android/contacts/calllog/CallDetailHistoryAdapter.java
@@ -64,19 +64,20 @@
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
         // Make sure we have a valid convertView to start with
-        if (convertView == null) {
-            convertView = mLayoutInflater.inflate(R.layout.call_detail_history_item, parent, false);
-        }
+        final View result  = convertView == null
+                ? mLayoutInflater.inflate(R.layout.call_detail_history_item, parent, false)
+                : convertView;
 
         PhoneCallDetails details = mPhoneCallDetails[position];
-        FrameLayout callTypeIconView = (FrameLayout) convertView.findViewById(R.id.call_type_icon);
-        TextView callTypeTextView = (TextView) convertView.findViewById(R.id.call_type_text);
-        TextView dateView = (TextView) convertView.findViewById(R.id.date);
-        TextView durationView = (TextView) convertView.findViewById(R.id.duration);
+        CallTypeIconsView callTypeIconView =
+                (CallTypeIconsView) result.findViewById(R.id.call_type_icon);
+        TextView callTypeTextView = (TextView) result.findViewById(R.id.call_type_text);
+        TextView dateView = (TextView) result.findViewById(R.id.date);
+        TextView durationView = (TextView) result.findViewById(R.id.duration);
 
         int callType = details.callTypes[0];
-        callTypeIconView.removeAllViews();
-        mCallTypeHelper.inflateCallTypeIcon(callType, callTypeIconView);
+        callTypeIconView.clear();
+        callTypeIconView.add(callType);
         callTypeTextView.setText(mCallTypeHelper.getCallTypeText(callType));
         // Set the date.
         CharSequence dateValue = DateUtils.formatDateRange(mContext, details.date, details.date,
@@ -91,7 +92,7 @@
             durationView.setText(formatDuration(details.duration));
         }
 
-        return convertView;
+        return result;
     }
 
     private String formatDuration(long elapsedSeconds) {
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index ae22489..0999b44 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -313,11 +313,7 @@
             mPreDrawListener = null;
 
             Resources resources = getResources();
-            LayoutInflater layoutInflater = getActivity().getLayoutInflater();
-            CallTypeHelper callTypeHelper = new CallTypeHelper(resources, layoutInflater);
-            Drawable callDrawable = resources.getDrawable(R.drawable.ic_dial_action_call);
-            Drawable playDrawable = resources.getDrawable(
-                    R.drawable.ic_call_log_list_action_play);
+            CallTypeHelper callTypeHelper = new CallTypeHelper(resources);
 
             mContactPhotoManager = ContactPhotoManager.getInstance(getActivity());
             mPhoneNumberHelper = new PhoneNumberHelper(getResources(), mVoiceMailNumber);
diff --git a/src/com/android/contacts/calllog/CallTypeHelper.java b/src/com/android/contacts/calllog/CallTypeHelper.java
index 465e2bf..d27d4f9 100644
--- a/src/com/android/contacts/calllog/CallTypeHelper.java
+++ b/src/com/android/contacts/calllog/CallTypeHelper.java
@@ -25,16 +25,11 @@
 import android.text.Spanned;
 import android.text.style.ForegroundColorSpan;
 import android.text.style.StyleSpan;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
 
 /**
  * Helper class to perform operations related to call types.
  */
 public class CallTypeHelper {
-    /** Used to create the views for the call types. */
-    private final LayoutInflater mLayoutInflater;
     /** Name used to identify incoming calls. */
     private final CharSequence mIncomingName;
     /** Name used to identify outgoing calls. */
@@ -48,8 +43,7 @@
     /** Name used to identify new voicemail calls. */
     private final CharSequence mNewVoicemailName;
 
-    public CallTypeHelper(Resources resources, LayoutInflater layoutInflater) {
-        mLayoutInflater = layoutInflater;
+    public CallTypeHelper(Resources resources) {
         // Cache these values so that we do not need to look them up each time.
         mIncomingName = resources.getString(R.string.type_incoming);
         mOutgoingName = resources.getString(R.string.type_outgoing);
@@ -103,26 +97,6 @@
         }
     }
 
-    /** Returns a new view for the icon to be used to represent a given call type. */
-    public View inflateCallTypeIcon(int callType, ViewGroup root) {
-        switch (callType) {
-            case Calls.INCOMING_TYPE:
-                return mLayoutInflater.inflate(R.layout.call_log_incoming_call_icon, root);
-
-            case Calls.OUTGOING_TYPE:
-                return mLayoutInflater.inflate(R.layout.call_log_outgoing_call_icon, root);
-
-            case Calls.MISSED_TYPE:
-                return mLayoutInflater.inflate(R.layout.call_log_missed_call_icon, root);
-
-            case Calls.VOICEMAIL_TYPE:
-                return mLayoutInflater.inflate(R.layout.call_log_voicemail_icon, root);
-
-            default:
-                throw new IllegalArgumentException("invalid call type: " + callType);
-        }
-    }
-
     /** Creates a SpannableString for the given text which is bold and in the given color. */
     private CharSequence addBoldAndColor(CharSequence text, int color) {
         int flags = Spanned.SPAN_INCLUSIVE_INCLUSIVE;
diff --git a/src/com/android/contacts/calllog/CallTypeIconsView.java b/src/com/android/contacts/calllog/CallTypeIconsView.java
new file mode 100644
index 0000000..4fbe7d7
--- /dev/null
+++ b/src/com/android/contacts/calllog/CallTypeIconsView.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.calllog;
+
+import com.android.contacts.R;
+import com.google.common.collect.Lists;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.provider.CallLog.Calls;
+import android.util.AttributeSet;
+import android.view.View;
+
+import java.util.List;
+
+/**
+ * View that draws one or more symbols for different types of calls (missed calls, outgoing etc).
+ * The symbols are set up horizontally. As this view doesn't create subviews, it is better suited
+ * for ListView-recycling that a regular LinearLayout using ImageViews.
+ */
+public class CallTypeIconsView extends View {
+    private List<Integer> mCallTypes = Lists.newArrayListWithCapacity(3);
+    private Resources mResources;
+    private int mWidth;
+    private int mHeight;
+
+    public CallTypeIconsView(Context context) {
+        this(context, null);
+    }
+
+    public CallTypeIconsView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mResources = new Resources(context);
+    }
+
+    public void clear() {
+        mCallTypes.clear();
+        mWidth = 0;
+        mHeight = 0;
+        invalidate();
+    }
+
+    public void add(int callType) {
+        mCallTypes.add(callType);
+
+        final Drawable drawable = getCallTypeDrawable(callType);
+        mWidth += drawable.getIntrinsicWidth() + mResources.iconMargin;
+        mHeight = Math.max(mHeight, drawable.getIntrinsicHeight());
+        invalidate();
+    }
+
+    public int getCount() {
+        return mCallTypes.size();
+    }
+
+    public int getCallType(int index) {
+        return mCallTypes.get(index);
+    }
+
+    private Drawable getCallTypeDrawable(int callType) {
+        switch (callType) {
+            case Calls.INCOMING_TYPE:
+                return mResources.incoming;
+            case Calls.OUTGOING_TYPE:
+                return mResources.outgoing;
+            case Calls.MISSED_TYPE:
+                return mResources.missed;
+            case Calls.VOICEMAIL_TYPE:
+                return mResources.voicemail;
+            default:
+                throw new IllegalArgumentException("invalid call type: " + callType);
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        setMeasuredDimension(mWidth, mHeight);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        int left = 0;
+        for (Integer callType : mCallTypes) {
+            final Drawable drawable = getCallTypeDrawable(callType);
+            final int right = left + drawable.getIntrinsicWidth();
+            drawable.setBounds(left, 0, right, drawable.getIntrinsicHeight());
+            drawable.draw(canvas);
+            left = right + mResources.iconMargin;
+        }
+    }
+
+    private static class Resources {
+        public final Drawable incoming;
+        public final Drawable outgoing;
+        public final Drawable missed;
+        public final Drawable voicemail;
+        public final int iconMargin;
+
+        public Resources(Context context) {
+            final android.content.res.Resources r = context.getResources();
+            incoming = r.getDrawable(R.drawable.ic_call_incoming_holo_dark);
+            outgoing = r.getDrawable(R.drawable.ic_call_outgoing_holo_dark);
+            missed = r.getDrawable(R.drawable.ic_call_missed_holo_dark);
+            voicemail = r.getDrawable(R.drawable.ic_call_voicemail_holo_dark);
+            iconMargin = r.getDimensionPixelSize(R.dimen.call_log_icon_margin);
+        }
+    }
+}