Merge "Put the call log into a fragment and host it in a simple container activity"
diff --git a/res/layout/call_log_activity.xml b/res/layout/call_log_activity.xml
index f054b70..b391795 100644
--- a/res/layout/call_log_activity.xml
+++ b/res/layout/call_log_activity.xml
@@ -18,17 +18,8 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
 >
-    <ListView android:id="@android:id/list"
-        android:layout_width="match_parent" 
-        android:layout_height="match_parent"
-        android:scrollbarStyle="outsideOverlay"
-    />
-    
-    <TextView android:id="@android:id/empty"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:text="@string/recentCalls_empty"
-        android:gravity="center"
-        android:textAppearance="?android:attr/textAppearanceLarge"
-    />
+    <fragment class="com.android.contacts.calllog.CallLogFragment"
+            android:id="@+id/call_log_fragment"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
 </FrameLayout>
diff --git a/res/layout/call_log_fragment.xml b/res/layout/call_log_fragment.xml
new file mode 100644
index 0000000..2a27fcd
--- /dev/null
+++ b/res/layout/call_log_fragment.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+>
+    <ListView android:id="@android:id/list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scrollbarStyle="outsideOverlay"
+    />
+
+    <TextView android:id="@android:id/empty"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:text="@string/recentCalls_empty"
+        android:gravity="center"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+    />
+</FrameLayout>
diff --git a/src/com/android/contacts/activities/CallLogActivity.java b/src/com/android/contacts/activities/CallLogActivity.java
index 5672af9..13c3209 100644
--- a/src/com/android/contacts/activities/CallLogActivity.java
+++ b/src/com/android/contacts/activities/CallLogActivity.java
@@ -16,874 +16,46 @@
 
 package com.android.contacts.activities;
 
-import com.android.common.widget.GroupingListAdapter;
-import com.android.contacts.CallDetailActivity;
 import com.android.contacts.ContactsSearchManager;
-import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
-import com.android.internal.telephony.CallerInfo;
+import com.android.contacts.calllog.CallLogFragment;
 import com.android.internal.telephony.ITelephony;
 
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.ListActivity;
-import android.app.ProgressDialog;
+import android.app.Activity;
 import android.content.ActivityNotFoundException;
-import android.content.AsyncQueryHandler;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.DialogInterface.OnClickListener;
-import android.database.CharArrayBuffer;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabaseCorruptException;
-import android.database.sqlite.SQLiteDiskIOException;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteFullException;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.provider.CallLog;
-import android.provider.CallLog.Calls;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.SipAddress;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.Intents.Insert;
-import android.provider.ContactsContract.PhoneLookup;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
 import android.util.Log;
-import android.view.ContextMenu;
 import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
 import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.widget.AdapterView;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import java.lang.ref.WeakReference;
-import java.util.HashMap;
-import java.util.LinkedList;
 
 /**
  * Displays a list of call log entries.
  */
-public class CallLogActivity extends ListActivity
-        implements View.OnCreateContextMenuListener {
+public class CallLogActivity extends Activity {
     private static final String TAG = "CallLogActivity";
 
-    /** The projection to use when querying the call log table */
-    static final String[] CALL_LOG_PROJECTION = new String[] {
-            Calls._ID,
-            Calls.NUMBER,
-            Calls.DATE,
-            Calls.DURATION,
-            Calls.TYPE,
-            Calls.CACHED_NAME,
-            Calls.CACHED_NUMBER_TYPE,
-            Calls.CACHED_NUMBER_LABEL,
-            Calls.COUNTRY_ISO};
-
-    static final int ID_COLUMN_INDEX = 0;
-    static final int NUMBER_COLUMN_INDEX = 1;
-    static final int DATE_COLUMN_INDEX = 2;
-    static final int DURATION_COLUMN_INDEX = 3;
-    static final int CALL_TYPE_COLUMN_INDEX = 4;
-    static final int CALLER_NAME_COLUMN_INDEX = 5;
-    static final int CALLER_NUMBERTYPE_COLUMN_INDEX = 6;
-    static final int CALLER_NUMBERLABEL_COLUMN_INDEX = 7;
-    static final int COUNTRY_ISO_COLUMN_INDEX = 8;
-
-    /** The projection to use when querying the phones table */
-    static final String[] PHONES_PROJECTION = new String[] {
-            PhoneLookup._ID,
-            PhoneLookup.DISPLAY_NAME,
-            PhoneLookup.TYPE,
-            PhoneLookup.LABEL,
-            PhoneLookup.NUMBER,
-            PhoneLookup.NORMALIZED_NUMBER};
-
-    static final int PERSON_ID_COLUMN_INDEX = 0;
-    static final int NAME_COLUMN_INDEX = 1;
-    static final int PHONE_TYPE_COLUMN_INDEX = 2;
-    static final int LABEL_COLUMN_INDEX = 3;
-    static final int MATCHED_NUMBER_COLUMN_INDEX = 4;
-    static final int NORMALIZED_NUMBER_COLUMN_INDEX = 5;
-
-    private static final int MENU_ITEM_DELETE = 1;
-    private static final int MENU_ITEM_DELETE_ALL = 2;
-    private static final int MENU_ITEM_VIEW_CONTACTS = 3;
-
-    private static final int QUERY_TOKEN = 53;
-    private static final int UPDATE_TOKEN = 54;
-
-    private static final int DIALOG_CONFIRM_DELETE_ALL = 1;
-
-    CallLogAdapter mAdapter;
-    private QueryHandler mQueryHandler;
-    String mVoiceMailNumber;
-    private String mCurrentCountryIso;
-
-    private boolean mScrollToTop;
-
-    static final class ContactInfo {
-        public long personId;
-        public String name;
-        public int type;
-        public String label;
-        public String number;
-        public String formattedNumber;
-        public String normalizedNumber;
-
-        public static ContactInfo EMPTY = new ContactInfo();
-    }
-
-    public static final class CallLogListItemViews {
-        TextView line1View;
-        TextView labelView;
-        TextView numberView;
-        TextView dateView;
-        ImageView iconView;
-        View callView;
-        ImageView groupIndicator;
-        TextView groupSize;
-    }
-
-    static final class CallerInfoQuery {
-        String number;
-        int position;
-        String name;
-        int numberType;
-        String numberLabel;
-    }
-
-    /** Adapter class to fill in data for the Call Log */
-    final class CallLogAdapter extends GroupingListAdapter
-            implements Runnable, ViewTreeObserver.OnPreDrawListener, View.OnClickListener {
-        HashMap<String,ContactInfo> mContactInfo;
-        private final LinkedList<CallerInfoQuery> mRequests;
-        private volatile boolean mDone;
-        private boolean mLoading = true;
-        ViewTreeObserver.OnPreDrawListener mPreDrawListener;
-        private static final int REDRAW = 1;
-        private static final int START_THREAD = 2;
-        private boolean mFirst;
-        private Thread mCallerIdThread;
-
-        private CharSequence[] mLabelArray;
-
-        private Drawable mDrawableIncoming;
-        private Drawable mDrawableOutgoing;
-        private Drawable mDrawableMissed;
-
-        /**
-         * Reusable char array buffers.
-         */
-        private CharArrayBuffer mBuffer1 = new CharArrayBuffer(128);
-        private CharArrayBuffer mBuffer2 = new CharArrayBuffer(128);
-
-        public void onClick(View view) {
-            String number = (String) view.getTag();
-            if (!TextUtils.isEmpty(number)) {
-                // Here, "number" can either be a PSTN phone number or a
-                // SIP address.  So turn it into either a tel: URI or a
-                // sip: URI, as appropriate.
-                Uri callUri;
-                if (PhoneNumberUtils.isUriNumber(number)) {
-                    callUri = Uri.fromParts("sip", number, null);
-                } else {
-                    callUri = Uri.fromParts("tel", number, null);
-                }
-                startActivity(new Intent(Intent.ACTION_CALL_PRIVILEGED, callUri));
-            }
-        }
-
-        public boolean onPreDraw() {
-            if (mFirst) {
-                mHandler.sendEmptyMessageDelayed(START_THREAD, 1000);
-                mFirst = false;
-            }
-            return true;
-        }
-
-        private Handler mHandler = new Handler() {
-            @Override
-            public void handleMessage(Message msg) {
-                switch (msg.what) {
-                    case REDRAW:
-                        notifyDataSetChanged();
-                        break;
-                    case START_THREAD:
-                        startRequestProcessing();
-                        break;
-                }
-            }
-        };
-
-        public CallLogAdapter() {
-            super(CallLogActivity.this);
-
-            mContactInfo = new HashMap<String,ContactInfo>();
-            mRequests = new LinkedList<CallerInfoQuery>();
-            mPreDrawListener = null;
-
-            mDrawableIncoming = getResources().getDrawable(
-                    R.drawable.ic_call_log_list_incoming_call);
-            mDrawableOutgoing = getResources().getDrawable(
-                    R.drawable.ic_call_log_list_outgoing_call);
-            mDrawableMissed = getResources().getDrawable(
-                    R.drawable.ic_call_log_list_missed_call);
-            mLabelArray = getResources().getTextArray(com.android.internal.R.array.phoneTypes);
-        }
-
-        /**
-         * Requery on background thread when {@link Cursor} changes.
-         */
-        @Override
-        protected void onContentChanged() {
-            // Start async requery
-            startQuery();
-        }
-
-        void setLoading(boolean loading) {
-            mLoading = loading;
-        }
-
-        @Override
-        public boolean isEmpty() {
-            if (mLoading) {
-                // We don't want the empty state to show when loading.
-                return false;
-            } else {
-                return super.isEmpty();
-            }
-        }
-
-        public ContactInfo getContactInfo(String number) {
-            return mContactInfo.get(number);
-        }
-
-        public void startRequestProcessing() {
-            mDone = false;
-            mCallerIdThread = new Thread(this);
-            mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
-            mCallerIdThread.start();
-        }
-
-        public void stopRequestProcessing() {
-            mDone = true;
-            if (mCallerIdThread != null) mCallerIdThread.interrupt();
-        }
-
-        public void clearCache() {
-            synchronized (mContactInfo) {
-                mContactInfo.clear();
-            }
-        }
-
-        private void updateCallLog(CallerInfoQuery ciq, ContactInfo ci) {
-            // Check if they are different. If not, don't update.
-            if (TextUtils.equals(ciq.name, ci.name)
-                    && TextUtils.equals(ciq.numberLabel, ci.label)
-                    && ciq.numberType == ci.type) {
-                return;
-            }
-            ContentValues values = new ContentValues(3);
-            values.put(Calls.CACHED_NAME, ci.name);
-            values.put(Calls.CACHED_NUMBER_TYPE, ci.type);
-            values.put(Calls.CACHED_NUMBER_LABEL, ci.label);
-
-            try {
-                CallLogActivity.this.getContentResolver().update(Calls.CONTENT_URI, values,
-                        Calls.NUMBER + "='" + ciq.number + "'", null);
-            } catch (SQLiteDiskIOException e) {
-                Log.w(TAG, "Exception while updating call info", e);
-            } catch (SQLiteFullException e) {
-                Log.w(TAG, "Exception while updating call info", e);
-            } catch (SQLiteDatabaseCorruptException e) {
-                Log.w(TAG, "Exception while updating call info", e);
-            }
-        }
-
-        private void enqueueRequest(String number, int position,
-                String name, int numberType, String numberLabel) {
-            CallerInfoQuery ciq = new CallerInfoQuery();
-            ciq.number = number;
-            ciq.position = position;
-            ciq.name = name;
-            ciq.numberType = numberType;
-            ciq.numberLabel = numberLabel;
-            synchronized (mRequests) {
-                mRequests.add(ciq);
-                mRequests.notifyAll();
-            }
-        }
-
-        private boolean queryContactInfo(CallerInfoQuery ciq) {
-            // First check if there was a prior request for the same number
-            // that was already satisfied
-            ContactInfo info = mContactInfo.get(ciq.number);
-            boolean needNotify = false;
-            if (info != null && info != ContactInfo.EMPTY) {
-                return true;
-            } else {
-                // Ok, do a fresh Contacts lookup for ciq.number.
-                boolean infoUpdated = false;
-
-                if (PhoneNumberUtils.isUriNumber(ciq.number)) {
-                    // This "number" is really a SIP address.
-
-                    // TODO: This code is duplicated from the
-                    // CallerInfoAsyncQuery class.  To avoid that, could the
-                    // code here just use CallerInfoAsyncQuery, rather than
-                    // manually running ContentResolver.query() itself?
-
-                    // We look up SIP addresses directly in the Data table:
-                    Uri contactRef = Data.CONTENT_URI;
-
-                    // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
-                    //
-                    // Also note we use "upper(data1)" in the WHERE clause, and
-                    // uppercase the incoming SIP address, in order to do a
-                    // case-insensitive match.
-                    //
-                    // TODO: May also need to normalize by adding "sip:" as a
-                    // prefix, if we start storing SIP addresses that way in the
-                    // database.
-                    String selection = "upper(" + Data.DATA1 + ")=?"
-                            + " AND "
-                            + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'";
-                    String[] selectionArgs = new String[] { ciq.number.toUpperCase() };
-
-                    Cursor dataTableCursor =
-                            CallLogActivity.this.getContentResolver().query(
-                                    contactRef,
-                                    null,  // projection
-                                    selection,  // selection
-                                    selectionArgs,  // selectionArgs
-                                    null);  // sortOrder
-
-                    if (dataTableCursor != null) {
-                        if (dataTableCursor.moveToFirst()) {
-                            info = new ContactInfo();
-
-                            // TODO: we could slightly speed this up using an
-                            // explicit projection (and thus not have to do
-                            // those getColumnIndex() calls) but the benefit is
-                            // very minimal.
-
-                            // Note the Data.CONTACT_ID column here is
-                            // equivalent to the PERSON_ID_COLUMN_INDEX column
-                            // we use with "phonesCursor" below.
-                            info.personId = dataTableCursor.getLong(
-                                    dataTableCursor.getColumnIndex(Data.CONTACT_ID));
-                            info.name = dataTableCursor.getString(
-                                    dataTableCursor.getColumnIndex(Data.DISPLAY_NAME));
-                            // "type" and "label" are currently unused for SIP addresses
-                            info.type = SipAddress.TYPE_OTHER;
-                            info.label = null;
-
-                            // And "number" is the SIP address.
-                            // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
-                            info.number = dataTableCursor.getString(
-                                    dataTableCursor.getColumnIndex(Data.DATA1));
-                            info.normalizedNumber = null;  // meaningless for SIP addresses
-
-                            infoUpdated = true;
-                        }
-                        dataTableCursor.close();
-                    }
-                } else {
-                    // "number" is a regular phone number, so use the
-                    // PhoneLookup table:
-                    Cursor phonesCursor =
-                            CallLogActivity.this.getContentResolver().query(
-                                Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
-                                        Uri.encode(ciq.number)),
-                                PHONES_PROJECTION, null, null, null);
-                    if (phonesCursor != null) {
-                        if (phonesCursor.moveToFirst()) {
-                            info = new ContactInfo();
-                            info.personId = phonesCursor.getLong(PERSON_ID_COLUMN_INDEX);
-                            info.name = phonesCursor.getString(NAME_COLUMN_INDEX);
-                            info.type = phonesCursor.getInt(PHONE_TYPE_COLUMN_INDEX);
-                            info.label = phonesCursor.getString(LABEL_COLUMN_INDEX);
-                            info.number = phonesCursor.getString(MATCHED_NUMBER_COLUMN_INDEX);
-                            info.normalizedNumber =
-                                    phonesCursor.getString(NORMALIZED_NUMBER_COLUMN_INDEX);
-
-                            infoUpdated = true;
-                        }
-                        phonesCursor.close();
-                    }
-                }
-
-                if (infoUpdated) {
-                    // New incoming phone number invalidates our formatted
-                    // cache. Any cache fills happen only on the GUI thread.
-                    info.formattedNumber = null;
-
-                    mContactInfo.put(ciq.number, info);
-
-                    // Inform list to update this item, if in view
-                    needNotify = true;
-                }
-            }
-            if (info != null) {
-                updateCallLog(ciq, info);
-            }
-            return needNotify;
-        }
-
-        /*
-         * Handles requests for contact name and number type
-         * @see java.lang.Runnable#run()
-         */
-        public void run() {
-            boolean needNotify = false;
-            while (!mDone) {
-                CallerInfoQuery ciq = null;
-                synchronized (mRequests) {
-                    if (!mRequests.isEmpty()) {
-                        ciq = mRequests.removeFirst();
-                    } else {
-                        if (needNotify) {
-                            needNotify = false;
-                            mHandler.sendEmptyMessage(REDRAW);
-                        }
-                        try {
-                            mRequests.wait(1000);
-                        } catch (InterruptedException ie) {
-                            // Ignore and continue processing requests
-                        }
-                    }
-                }
-                if (ciq != null && queryContactInfo(ciq)) {
-                    needNotify = true;
-                }
-            }
-        }
-
-        @Override
-        protected void addGroups(Cursor cursor) {
-
-            int count = cursor.getCount();
-            if (count == 0) {
-                return;
-            }
-
-            int groupItemCount = 1;
-
-            CharArrayBuffer currentValue = mBuffer1;
-            CharArrayBuffer value = mBuffer2;
-            cursor.moveToFirst();
-            cursor.copyStringToBuffer(NUMBER_COLUMN_INDEX, currentValue);
-            int currentCallType = cursor.getInt(CALL_TYPE_COLUMN_INDEX);
-            for (int i = 1; i < count; i++) {
-                cursor.moveToNext();
-                cursor.copyStringToBuffer(NUMBER_COLUMN_INDEX, value);
-                boolean sameNumber = equalPhoneNumbers(value, currentValue);
-
-                // Group adjacent calls with the same number. Make an exception
-                // for the latest item if it was a missed call.  We don't want
-                // a missed call to be hidden inside a group.
-                if (sameNumber && currentCallType != Calls.MISSED_TYPE) {
-                    groupItemCount++;
-                } else {
-                    if (groupItemCount > 1) {
-                        addGroup(i - groupItemCount, groupItemCount, false);
-                    }
-
-                    groupItemCount = 1;
-
-                    // Swap buffers
-                    CharArrayBuffer temp = currentValue;
-                    currentValue = value;
-                    value = temp;
-
-                    // If we have just examined a row following a missed call, make
-                    // sure that it is grouped with subsequent calls from the same number
-                    // even if it was also missed.
-                    if (sameNumber && currentCallType == Calls.MISSED_TYPE) {
-                        currentCallType = 0;       // "not a missed call"
-                    } else {
-                        currentCallType = cursor.getInt(CALL_TYPE_COLUMN_INDEX);
-                    }
-                }
-            }
-            if (groupItemCount > 1) {
-                addGroup(count - groupItemCount, groupItemCount, false);
-            }
-        }
-
-        protected boolean equalPhoneNumbers(CharArrayBuffer buffer1, CharArrayBuffer buffer2) {
-
-            // TODO add PhoneNumberUtils.compare(CharSequence, CharSequence) to avoid
-            // string allocation
-            return PhoneNumberUtils.compare(new String(buffer1.data, 0, buffer1.sizeCopied),
-                    new String(buffer2.data, 0, buffer2.sizeCopied));
-        }
-
-
-        @Override
-        protected View newStandAloneView(Context context, ViewGroup parent) {
-            LayoutInflater inflater =
-                    (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-            View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
-            findAndCacheViews(view);
-            return view;
-        }
-
-        @Override
-        protected void bindStandAloneView(View view, Context context, Cursor cursor) {
-            bindView(context, view, cursor);
-        }
-
-        @Override
-        protected View newChildView(Context context, ViewGroup parent) {
-            LayoutInflater inflater =
-                    (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-            View view = inflater.inflate(R.layout.call_log_list_child_item, parent, false);
-            findAndCacheViews(view);
-            return view;
-        }
-
-        @Override
-        protected void bindChildView(View view, Context context, Cursor cursor) {
-            bindView(context, view, cursor);
-        }
-
-        @Override
-        protected View newGroupView(Context context, ViewGroup parent) {
-            LayoutInflater inflater =
-                    (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-            View view = inflater.inflate(R.layout.call_log_list_group_item, parent, false);
-            findAndCacheViews(view);
-            return view;
-        }
-
-        @Override
-        protected void bindGroupView(View view, Context context, Cursor cursor, int groupSize,
-                boolean expanded) {
-            final CallLogListItemViews views = (CallLogListItemViews) view.getTag();
-            int groupIndicator = expanded
-                    ? com.android.internal.R.drawable.expander_ic_maximized
-                    : com.android.internal.R.drawable.expander_ic_minimized;
-            views.groupIndicator.setImageResource(groupIndicator);
-            views.groupSize.setText("(" + groupSize + ")");
-            bindView(context, view, cursor);
-        }
-
-        private void findAndCacheViews(View view) {
-
-            // Get the views to bind to
-            CallLogListItemViews views = new CallLogListItemViews();
-            views.line1View = (TextView) view.findViewById(R.id.line1);
-            views.labelView = (TextView) view.findViewById(R.id.label);
-            views.numberView = (TextView) view.findViewById(R.id.number);
-            views.dateView = (TextView) view.findViewById(R.id.date);
-            views.iconView = (ImageView) view.findViewById(R.id.call_type_icon);
-            views.callView = view.findViewById(R.id.call_icon);
-            views.callView.setOnClickListener(this);
-            views.groupIndicator = (ImageView) view.findViewById(R.id.groupIndicator);
-            views.groupSize = (TextView) view.findViewById(R.id.groupSize);
-            view.setTag(views);
-        }
-
-        public void bindView(Context context, View view, Cursor c) {
-            final CallLogListItemViews views = (CallLogListItemViews) view.getTag();
-
-            String number = c.getString(NUMBER_COLUMN_INDEX);
-            String formattedNumber = null;
-            String callerName = c.getString(CALLER_NAME_COLUMN_INDEX);
-            int callerNumberType = c.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
-            String callerNumberLabel = c.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
-            String countryIso = c.getString(COUNTRY_ISO_COLUMN_INDEX);
-            // Store away the number so we can call it directly if you click on the call icon
-            views.callView.setTag(number);
-
-            // Lookup contacts with this number
-            ContactInfo info = mContactInfo.get(number);
-            if (info == null) {
-                // Mark it as empty and queue up a request to find the name
-                // The db request should happen on a non-UI thread
-                info = ContactInfo.EMPTY;
-                mContactInfo.put(number, info);
-                enqueueRequest(number, c.getPosition(),
-                        callerName, callerNumberType, callerNumberLabel);
-            } else if (info != ContactInfo.EMPTY) { // Has been queried
-                // Check if any data is different from the data cached in the
-                // calls db. If so, queue the request so that we can update
-                // the calls db.
-                if (!TextUtils.equals(info.name, callerName)
-                        || info.type != callerNumberType
-                        || !TextUtils.equals(info.label, callerNumberLabel)) {
-                    // Something is amiss, so sync up.
-                    enqueueRequest(number, c.getPosition(),
-                            callerName, callerNumberType, callerNumberLabel);
-                }
-
-                // Format and cache phone number for found contact
-                if (info.formattedNumber == null) {
-                    info.formattedNumber =
-                            formatPhoneNumber(info.number, info.normalizedNumber, countryIso);
-                }
-                formattedNumber = info.formattedNumber;
-            }
-
-            String name = info.name;
-            int ntype = info.type;
-            String label = info.label;
-            // If there's no name cached in our hashmap, but there's one in the
-            // calls db, use the one in the calls db. Otherwise the name in our
-            // hashmap is more recent, so it has precedence.
-            if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(callerName)) {
-                name = callerName;
-                ntype = callerNumberType;
-                label = callerNumberLabel;
-
-                // Format the cached call_log phone number
-                formattedNumber = formatPhoneNumber(number, null, countryIso);
-            }
-            // Set the text lines and call icon.
-            // Assumes the call back feature is on most of the
-            // time. For private and unknown numbers: hide it.
-            views.callView.setVisibility(View.VISIBLE);
-
-            if (!TextUtils.isEmpty(name)) {
-                views.line1View.setText(name);
-                views.labelView.setVisibility(View.VISIBLE);
-
-                // "type" and "label" are currently unused for SIP addresses.
-                CharSequence numberLabel = null;
-                if (!PhoneNumberUtils.isUriNumber(number)) {
-                    numberLabel = Phone.getDisplayLabel(context, ntype, label,
-                            mLabelArray);
-                }
-                views.numberView.setVisibility(View.VISIBLE);
-                views.numberView.setText(formattedNumber);
-                if (!TextUtils.isEmpty(numberLabel)) {
-                    views.labelView.setText(numberLabel);
-                    views.labelView.setVisibility(View.VISIBLE);
-
-                    // Zero out the numberView's left margin (see below)
-                    ViewGroup.MarginLayoutParams numberLP =
-                            (ViewGroup.MarginLayoutParams) views.numberView.getLayoutParams();
-                    numberLP.leftMargin = 0;
-                    views.numberView.setLayoutParams(numberLP);
-                } else {
-                    // There's nothing to display in views.labelView, so hide it.
-                    // We can't set it to View.GONE, since it's the anchor for
-                    // numberView in the RelativeLayout, so make it INVISIBLE.
-                    //   Also, we need to manually *subtract* some left margin from
-                    // numberView to compensate for the right margin built in to
-                    // labelView (otherwise the number will be indented by a very
-                    // slight amount).
-                    //   TODO: a cleaner fix would be to contain both the label and
-                    // number inside a LinearLayout, and then set labelView *and*
-                    // its padding to GONE when there's no label to display.
-                    views.labelView.setText(null);
-                    views.labelView.setVisibility(View.INVISIBLE);
-
-                    ViewGroup.MarginLayoutParams labelLP =
-                            (ViewGroup.MarginLayoutParams) views.labelView.getLayoutParams();
-                    ViewGroup.MarginLayoutParams numberLP =
-                            (ViewGroup.MarginLayoutParams) views.numberView.getLayoutParams();
-                    // Equivalent to setting android:layout_marginLeft in XML
-                    numberLP.leftMargin = -labelLP.rightMargin;
-                    views.numberView.setLayoutParams(numberLP);
-                }
-            } else {
-                if (number.equals(CallerInfo.UNKNOWN_NUMBER)) {
-                    number = getString(R.string.unknown);
-                    views.callView.setVisibility(View.INVISIBLE);
-                } else if (number.equals(CallerInfo.PRIVATE_NUMBER)) {
-                    number = getString(R.string.private_num);
-                    views.callView.setVisibility(View.INVISIBLE);
-                } else if (number.equals(CallerInfo.PAYPHONE_NUMBER)) {
-                    number = getString(R.string.payphone);
-                } else if (PhoneNumberUtils.extractNetworkPortion(number)
-                                .equals(mVoiceMailNumber)) {
-                    number = getString(R.string.voicemail);
-                } else {
-                    // Just a raw number, and no cache, so format it nicely
-                    number = formatPhoneNumber(number, null, countryIso);
-                }
-
-                views.line1View.setText(number);
-                views.numberView.setVisibility(View.GONE);
-                views.labelView.setVisibility(View.GONE);
-            }
-
-            long date = c.getLong(DATE_COLUMN_INDEX);
-
-            // Set the date/time field by mixing relative and absolute times.
-            int flags = DateUtils.FORMAT_ABBREV_RELATIVE;
-
-            views.dateView.setText(DateUtils.getRelativeTimeSpanString(date,
-                    System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, flags));
-
-            if (views.iconView != null) {
-                int type = c.getInt(CALL_TYPE_COLUMN_INDEX);
-                // Set the icon
-                switch (type) {
-                    case Calls.INCOMING_TYPE:
-                        views.iconView.setImageDrawable(mDrawableIncoming);
-                        break;
-
-                    case Calls.OUTGOING_TYPE:
-                        views.iconView.setImageDrawable(mDrawableOutgoing);
-                        break;
-
-                    case Calls.MISSED_TYPE:
-                        views.iconView.setImageDrawable(mDrawableMissed);
-                        break;
-                }
-            }
-
-            // Listen for the first draw
-            if (mPreDrawListener == null) {
-                mFirst = true;
-                mPreDrawListener = this;
-                view.getViewTreeObserver().addOnPreDrawListener(this);
-            }
-        }
-    }
-
-    private static final class QueryHandler extends AsyncQueryHandler {
-        private final WeakReference<CallLogActivity> mActivity;
-
-        /**
-         * Simple handler that wraps background calls to catch
-         * {@link SQLiteException}, such as when the disk is full.
-         */
-        protected class CatchingWorkerHandler extends AsyncQueryHandler.WorkerHandler {
-            public CatchingWorkerHandler(Looper looper) {
-                super(looper);
-            }
-
-            @Override
-            public void handleMessage(Message msg) {
-                try {
-                    // Perform same query while catching any exceptions
-                    super.handleMessage(msg);
-                } catch (SQLiteDiskIOException e) {
-                    Log.w(TAG, "Exception on background worker thread", e);
-                } catch (SQLiteFullException e) {
-                    Log.w(TAG, "Exception on background worker thread", e);
-                } catch (SQLiteDatabaseCorruptException e) {
-                    Log.w(TAG, "Exception on background worker thread", e);
-                }
-            }
-        }
-
-        @Override
-        protected Handler createHandler(Looper looper) {
-            // Provide our special handler that catches exceptions
-            return new CatchingWorkerHandler(looper);
-        }
-
-        public QueryHandler(Context context) {
-            super(context.getContentResolver());
-            mActivity = new WeakReference<CallLogActivity>(
-                    (CallLogActivity) context);
-        }
-
-        @Override
-        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
-            final CallLogActivity activity = mActivity.get();
-            if (activity != null && !activity.isFinishing()) {
-                final CallLogActivity.CallLogAdapter callsAdapter = activity.mAdapter;
-                callsAdapter.setLoading(false);
-                callsAdapter.changeCursor(cursor);
-                if (activity.mScrollToTop) {
-                    if (activity.mList.getFirstVisiblePosition() > 5) {
-                        activity.mList.setSelection(5);
-                    }
-                    activity.mList.smoothScrollToPosition(0);
-                    activity.mScrollToTop = false;
-                }
-            } else {
-                cursor.close();
-            }
-        }
-    }
+    private CallLogFragment mFragment;
 
     @Override
-    protected void onCreate(Bundle state) {
-        super.onCreate(state);
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
 
         setContentView(R.layout.call_log_activity);
 
         // Typing here goes to the dialer
         setDefaultKeyMode(DEFAULT_KEYS_DIALER);
 
-        mAdapter = new CallLogAdapter();
-        getListView().setOnCreateContextMenuListener(this);
-        setListAdapter(mAdapter);
-
-        mVoiceMailNumber = ((TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE))
-                .getVoiceMailNumber();
-        mQueryHandler = new QueryHandler(this);
-
-        mCurrentCountryIso = ContactsUtils.getCurrentCountryIso(this);
+        mFragment = (CallLogFragment) getFragmentManager().findFragmentById(
+                R.id.call_log_fragment);
     }
 
-    @Override
-    protected void onStart() {
-        mScrollToTop = true;
-        super.onStart();
-    }
-
-    @Override
-    protected void onResume() {
-        // The adapter caches looked up numbers, clear it so they will get
-        // looked up again.
-        if (mAdapter != null) {
-            mAdapter.clearCache();
-        }
-
-        startQuery();
-        resetNewCallsFlag();
-
-        super.onResume();
-
-        mAdapter.mPreDrawListener = null; // Let it restart the thread after next draw
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-
-        // Kill the requests thread
-        mAdapter.stopRequestProcessing();
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        mAdapter.stopRequestProcessing();
-        mAdapter.changeCursor(null);
+    public CallLogFragment getFragment() {
+        return mFragment;
     }
 
     @Override
@@ -894,10 +66,10 @@
         // immediately receive focus if the keyguard screen is above it.
         if (hasFocus) {
             try {
-                ITelephony iTelephony =
+                ITelephony telephony =
                         ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
-                if (iTelephony != null) {
-                    iTelephony.cancelMissedCallsNotification();
+                if (telephony != null) {
+                    telephony.cancelMissedCallsNotification();
                 } else {
                     Log.w(TAG, "Telephony service is null, can't call " +
                             "cancelMissedCallsNotification");
@@ -908,240 +80,6 @@
         }
     }
 
-    /**
-     * Format the given phone number
-     *
-     * @param number the number to be formatted.
-     * @param normalizedNumber the normalized number of the given number.
-     * @param countryIso the ISO 3166-1 two letters country code, the country's
-     *        convention will be used to format the number if the normalized
-     *        phone is null.
-     *
-     * @return the formatted number, or the given number if it was formatted.
-     */
-    private String formatPhoneNumber(String number, String normalizedNumber, String countryIso) {
-        if (TextUtils.isEmpty(number)) {
-            return "";
-        }
-        // If "number" is really a SIP address, don't try to do any formatting at all.
-        if (PhoneNumberUtils.isUriNumber(number)) {
-            return number;
-        }
-        if (TextUtils.isEmpty(countryIso)) {
-            countryIso = mCurrentCountryIso;
-        }
-        return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso);
-    }
-
-    private void resetNewCallsFlag() {
-        // Mark all "new" missed calls as not new anymore
-        StringBuilder where = new StringBuilder("type=");
-        where.append(Calls.MISSED_TYPE);
-        where.append(" AND new=1");
-
-        ContentValues values = new ContentValues(1);
-        values.put(Calls.NEW, "0");
-        mQueryHandler.startUpdate(UPDATE_TOKEN, null, Calls.CONTENT_URI,
-                values, where.toString(), null);
-    }
-
-    private void startQuery() {
-        mAdapter.setLoading(true);
-
-        // Cancel any pending queries
-        mQueryHandler.cancelOperation(QUERY_TOKEN);
-        mQueryHandler.startQuery(QUERY_TOKEN, null, Calls.CONTENT_URI,
-                CALL_LOG_PROJECTION, null, null, Calls.DEFAULT_SORT_ORDER);
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        menu.add(0, MENU_ITEM_DELETE_ALL, 0, R.string.recentCalls_deleteAll)
-                .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
-        return true;
-    }
-
-    @Override
-    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
-        AdapterView.AdapterContextMenuInfo menuInfo;
-        try {
-             menuInfo = (AdapterView.AdapterContextMenuInfo) menuInfoIn;
-        } catch (ClassCastException e) {
-            Log.e(TAG, "bad menuInfoIn", e);
-            return;
-        }
-
-        Cursor cursor = (Cursor) mAdapter.getItem(menuInfo.position);
-
-        String number = cursor.getString(NUMBER_COLUMN_INDEX);
-        Uri numberUri = null;
-        boolean isVoicemail = false;
-        boolean isSipNumber = false;
-        if (number.equals(CallerInfo.UNKNOWN_NUMBER)) {
-            number = getString(R.string.unknown);
-        } else if (number.equals(CallerInfo.PRIVATE_NUMBER)) {
-            number = getString(R.string.private_num);
-        } else if (number.equals(CallerInfo.PAYPHONE_NUMBER)) {
-            number = getString(R.string.payphone);
-        } else if (PhoneNumberUtils.extractNetworkPortion(number).equals(mVoiceMailNumber)) {
-            number = getString(R.string.voicemail);
-            numberUri = Uri.parse("voicemail:x");
-            isVoicemail = true;
-        } else if (PhoneNumberUtils.isUriNumber(number)) {
-            numberUri = Uri.fromParts("sip", number, null);
-            isSipNumber = true;
-        } else {
-            numberUri = Uri.fromParts("tel", number, null);
-        }
-
-        ContactInfo info = mAdapter.getContactInfo(number);
-        boolean contactInfoPresent = (info != null && info != ContactInfo.EMPTY);
-        if (contactInfoPresent) {
-            menu.setHeaderTitle(info.name);
-        } else {
-            menu.setHeaderTitle(number);
-        }
-
-        if (numberUri != null) {
-            Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, numberUri);
-            menu.add(0, 0, 0, getResources().getString(R.string.recentCalls_callNumber, number))
-                    .setIntent(intent);
-        }
-
-        if (contactInfoPresent) {
-            menu.add(0, 0, 0, R.string.menu_viewContact)
-                    .setIntent(new Intent(Intent.ACTION_VIEW,
-                            ContentUris.withAppendedId(Contacts.CONTENT_URI, info.personId)));
-        }
-
-        if (numberUri != null && !isVoicemail && !isSipNumber) {
-            menu.add(0, 0, 0, R.string.recentCalls_editNumberBeforeCall)
-                    .setIntent(new Intent(Intent.ACTION_DIAL, numberUri));
-            menu.add(0, 0, 0, R.string.menu_sendTextMessage)
-                    .setIntent(new Intent(Intent.ACTION_SENDTO,
-                            Uri.fromParts("sms", number, null)));
-        }
-
-        // "Add to contacts" item, if this entry isn't already associated with a contact
-        if (!contactInfoPresent && numberUri != null && !isVoicemail && !isSipNumber) {
-            // TODO: This item is currently disabled for SIP addresses, because
-            // the Insert.PHONE extra only works correctly for PSTN numbers.
-            //
-            // To fix this for SIP addresses, we need to:
-            // - define ContactsContract.Intents.Insert.SIP_ADDRESS, and use it here if
-            //   the current number is a SIP address
-            // - update the contacts UI code to handle Insert.SIP_ADDRESS by
-            //   updating the SipAddress field
-            // and then we can remove the "!isSipNumber" check above.
-
-            Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
-            intent.setType(Contacts.CONTENT_ITEM_TYPE);
-            intent.putExtra(Insert.PHONE, number);
-            menu.add(0, 0, 0, R.string.recentCalls_addToContact)
-                    .setIntent(intent);
-        }
-        menu.add(0, MENU_ITEM_DELETE, 0, R.string.recentCalls_removeFromRecentList);
-    }
-
-    @Override
-    protected Dialog onCreateDialog(int id, Bundle args) {
-        switch (id) {
-            case DIALOG_CONFIRM_DELETE_ALL:
-                final ContentResolver resolver = getContentResolver();
-                final OnClickListener okListener = new OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog, int which) {
-                        final ProgressDialog progressDialog = ProgressDialog.show(
-                                CallLogActivity.this,
-                                getString(R.string.clearCallLogProgress_title),
-                                "", true, false);
-                        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
-                            @Override
-                            protected Void doInBackground(Void... params) {
-                                resolver.delete(Calls.CONTENT_URI, null, null);
-                                return null;
-                            }
-                            @Override
-                            protected void onPostExecute(Void result) {
-                                progressDialog.dismiss();
-                                // TODO The change notification should do this automatically, but
-                                // it isn't working right now. Remove this when the change
-                                // notification is working properly.
-                                startQuery();
-                            }
-                        };
-                        // TODO: Once we have the API, we should configure this ProgressDialog
-                        // to only show up after a certain time (e.g. 150ms)
-                        progressDialog.show();
-                        task.execute();
-                    }
-                };
-                return new AlertDialog.Builder(this)
-                    .setTitle(R.string.clearCallLogConfirmation_title)
-                    .setIconAttribute(android.R.attr.alertDialogIcon)
-                    .setMessage(R.string.clearCallLogConfirmation)
-                    .setNegativeButton(android.R.string.cancel, null)
-                    .setPositiveButton(android.R.string.ok, okListener)
-                    .setCancelable(true)
-                    .create();
-        }
-        return null;
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case MENU_ITEM_DELETE_ALL: {
-                showDialog(DIALOG_CONFIRM_DELETE_ALL);
-                return true;
-            }
-
-            case MENU_ITEM_VIEW_CONTACTS: {
-                Intent intent = new Intent(Intent.ACTION_VIEW, Contacts.CONTENT_URI);
-                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                startActivity(intent);
-                return true;
-            }
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    @Override
-    public boolean onContextItemSelected(MenuItem item) {
-        // Convert the menu info to the proper type
-        AdapterView.AdapterContextMenuInfo menuInfo;
-        try {
-             menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
-        } catch (ClassCastException e) {
-            Log.e(TAG, "bad menuInfoIn", e);
-            return false;
-        }
-
-        switch (item.getItemId()) {
-            case MENU_ITEM_DELETE: {
-                Cursor cursor = (Cursor)mAdapter.getItem(menuInfo.position);
-                int groupSize = 1;
-                if (mAdapter.isGroupHeader(menuInfo.position)) {
-                    groupSize = mAdapter.getGroupSize(menuInfo.position);
-                }
-
-                StringBuilder sb = new StringBuilder();
-                for (int i = 0; i < groupSize; i++) {
-                    if (i != 0) {
-                        sb.append(",");
-                        cursor.moveToNext();
-                    }
-                    long id = cursor.getLong(ID_COLUMN_INDEX);
-                    sb.append(id);
-                }
-
-                getContentResolver().delete(Calls.CONTENT_URI, Calls._ID + " IN (" + sb + ")",
-                        null);
-            }
-        }
-        return super.onContextItemSelected(item);
-    }
-
     @Override
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         switch (keyCode) {
@@ -1177,104 +115,12 @@
                     // Fall through and try to call the contact
                 }
 
-                callEntry(getListView().getSelectedItemPosition());
+                mFragment.callSelectedEntry();
                 return true;
         }
         return super.onKeyUp(keyCode, event);
     }
 
-    /*
-     * Get the number from the Contacts, if available, since sometimes
-     * the number provided by caller id may not be formatted properly
-     * depending on the carrier (roaming) in use at the time of the
-     * incoming call.
-     * Logic : If the caller-id number starts with a "+", use it
-     *         Else if the number in the contacts starts with a "+", use that one
-     *         Else if the number in the contacts is longer, use that one
-     */
-    private String getBetterNumberFromContacts(String number) {
-        String matchingNumber = null;
-        // Look in the cache first. If it's not found then query the Phones db
-        ContactInfo ci = mAdapter.mContactInfo.get(number);
-        if (ci != null && ci != ContactInfo.EMPTY) {
-            matchingNumber = ci.number;
-        } else {
-            try {
-                Cursor phonesCursor =
-                    CallLogActivity.this.getContentResolver().query(
-                            Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
-                                    number),
-                    PHONES_PROJECTION, null, null, null);
-                if (phonesCursor != null) {
-                    if (phonesCursor.moveToFirst()) {
-                        matchingNumber = phonesCursor.getString(MATCHED_NUMBER_COLUMN_INDEX);
-                    }
-                    phonesCursor.close();
-                }
-            } catch (Exception e) {
-                // Use the number from the call log
-            }
-        }
-        if (!TextUtils.isEmpty(matchingNumber) &&
-                (matchingNumber.startsWith("+")
-                        || matchingNumber.length() > number.length())) {
-            number = matchingNumber;
-        }
-        return number;
-    }
-
-    private void callEntry(int position) {
-        if (position < 0) {
-            // In touch mode you may often not have something selected, so
-            // just call the first entry to make sure that [send] [send] calls the
-            // most recent entry.
-            position = 0;
-        }
-        final Cursor cursor = (Cursor)mAdapter.getItem(position);
-        if (cursor != null) {
-            String number = cursor.getString(NUMBER_COLUMN_INDEX);
-            if (TextUtils.isEmpty(number)
-                    || number.equals(CallerInfo.UNKNOWN_NUMBER)
-                    || number.equals(CallerInfo.PRIVATE_NUMBER)
-                    || number.equals(CallerInfo.PAYPHONE_NUMBER)) {
-                // This number can't be called, do nothing
-                return;
-            }
-            Intent intent;
-            // If "number" is really a SIP address, construct a sip: URI.
-            if (PhoneNumberUtils.isUriNumber(number)) {
-                intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
-                                    Uri.fromParts("sip", number, null));
-            } else {
-                // We're calling a regular PSTN phone number.
-                // Construct a tel: URI, but do some other possible cleanup first.
-                int callType = cursor.getInt(CALL_TYPE_COLUMN_INDEX);
-                if (!number.startsWith("+") &&
-                       (callType == Calls.INCOMING_TYPE
-                                || callType == Calls.MISSED_TYPE)) {
-                    // If the caller-id matches a contact with a better qualified number, use it
-                    number = getBetterNumberFromContacts(number);
-                }
-                intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
-                                    Uri.fromParts("tel", number, null));
-            }
-            intent.setFlags(
-                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            startActivity(intent);
-        }
-    }
-
-    @Override
-    protected void onListItemClick(ListView l, View v, int position, long id) {
-        if (mAdapter.isGroupHeader(position)) {
-            mAdapter.toggleGroup(position);
-        } else {
-            Intent intent = new Intent(this, CallDetailActivity.class);
-            intent.setData(ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI, id));
-            startActivity(intent);
-        }
-    }
-
     @Override
     public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
             boolean globalSearch) {
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
new file mode 100644
index 0000000..3850a0b
--- /dev/null
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -0,0 +1,1171 @@
+/*
+ * 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.common.widget.GroupingListAdapter;
+import com.android.contacts.CallDetailActivity;
+import com.android.contacts.ContactsUtils;
+import com.android.contacts.R;
+import com.android.internal.telephony.CallerInfo;
+
+import android.app.ListFragment;
+import android.content.AsyncQueryHandler;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.CharArrayBuffer;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabaseCorruptException;
+import android.database.sqlite.SQLiteDiskIOException;
+import android.database.sqlite.SQLiteException;
+import android.database.sqlite.SQLiteFullException;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.CallLog;
+import android.provider.CallLog.Calls;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Intents.Insert;
+import android.provider.ContactsContract.PhoneLookup;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.AdapterView;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.LinkedList;
+
+/**
+ * Displays a list of call log entries.
+ */
+public class CallLogFragment extends ListFragment
+        implements View.OnCreateContextMenuListener {
+    private static final String TAG = "CallLogFragment";
+
+    /** The query for the call log table */
+    private static final class CallLogQuery {
+        public static final String[] _PROJECTION = new String[] {
+                Calls._ID,
+                Calls.NUMBER,
+                Calls.DATE,
+                Calls.DURATION,
+                Calls.TYPE,
+                Calls.CACHED_NAME,
+                Calls.CACHED_NUMBER_TYPE,
+                Calls.CACHED_NUMBER_LABEL,
+                Calls.COUNTRY_ISO};
+
+        public static final int ID = 0;
+        public static final int NUMBER = 1;
+        public static final int DATE = 2;
+        public static final int DURATION = 3;
+        public static final int CALL_TYPE = 4;
+        public static final int CALLER_NAME = 5;
+        public static final int CALLER_NUMBERTYPE = 6;
+        public static final int CALLER_NUMBERLABEL = 7;
+        public static final int COUNTRY_ISO = 8;
+    }
+
+    /** The query to use for the phones table */
+    private static final class PhoneQuery {
+        public static final String[] _PROJECTION = new String[] {
+                PhoneLookup._ID,
+                PhoneLookup.DISPLAY_NAME,
+                PhoneLookup.TYPE,
+                PhoneLookup.LABEL,
+                PhoneLookup.NUMBER,
+                PhoneLookup.NORMALIZED_NUMBER};
+
+        public static final int PERSON_ID = 0;
+        public static final int NAME = 1;
+        public static final int PHONE_TYPE = 2;
+        public static final int LABEL = 3;
+        public static final int MATCHED_NUMBER = 4;
+        public static final int NORMALIZED_NUMBER = 5;
+    }
+
+    private static final class MenuItems {
+        public static final int DELETE = 1;
+    }
+
+    private static final class OptionsMenuItems {
+        public static final int DELETE_ALL = 1;
+    }
+
+    private static final int QUERY_TOKEN = 53;
+    private static final int UPDATE_TOKEN = 54;
+
+    private CallLogAdapter mAdapter;
+    private QueryHandler mQueryHandler;
+    private String mVoiceMailNumber;
+    private String mCurrentCountryIso;
+    private boolean mScrollToTop;
+
+    public static final class ContactInfo {
+        public long personId;
+        public String name;
+        public int type;
+        public String label;
+        public String number;
+        public String formattedNumber;
+        public String normalizedNumber;
+
+        public static ContactInfo EMPTY = new ContactInfo();
+    }
+
+    public static final class CallLogListItemViews {
+        public TextView line1View;
+        public TextView labelView;
+        public TextView numberView;
+        public TextView dateView;
+        public ImageView iconView;
+        public View callView;
+        public ImageView groupIndicator;
+        public TextView groupSize;
+    }
+
+    public static final class CallerInfoQuery {
+        public String number;
+        public int position;
+        public String name;
+        public int numberType;
+        public String numberLabel;
+    }
+
+    /** Adapter class to fill in data for the Call Log */
+    public final class CallLogAdapter extends GroupingListAdapter
+            implements Runnable, ViewTreeObserver.OnPreDrawListener, View.OnClickListener {
+        HashMap<String,ContactInfo> mContactInfo;
+        private final LinkedList<CallerInfoQuery> mRequests;
+        private volatile boolean mDone;
+        private boolean mLoading = true;
+        ViewTreeObserver.OnPreDrawListener mPreDrawListener;
+        private static final int REDRAW = 1;
+        private static final int START_THREAD = 2;
+        private boolean mFirst;
+        private Thread mCallerIdThread;
+
+        private CharSequence[] mLabelArray;
+
+        private Drawable mDrawableIncoming;
+        private Drawable mDrawableOutgoing;
+        private Drawable mDrawableMissed;
+
+        /**
+         * Reusable char array buffers.
+         */
+        private CharArrayBuffer mBuffer1 = new CharArrayBuffer(128);
+        private CharArrayBuffer mBuffer2 = new CharArrayBuffer(128);
+
+        @Override
+        public void onClick(View view) {
+            String number = (String) view.getTag();
+            if (!TextUtils.isEmpty(number)) {
+                // Here, "number" can either be a PSTN phone number or a
+                // SIP address.  So turn it into either a tel: URI or a
+                // sip: URI, as appropriate.
+                Uri callUri;
+                if (PhoneNumberUtils.isUriNumber(number)) {
+                    callUri = Uri.fromParts("sip", number, null);
+                } else {
+                    callUri = Uri.fromParts("tel", number, null);
+                }
+                startActivity(new Intent(Intent.ACTION_CALL_PRIVILEGED, callUri));
+            }
+        }
+
+        @Override
+        public boolean onPreDraw() {
+            if (mFirst) {
+                mHandler.sendEmptyMessageDelayed(START_THREAD, 1000);
+                mFirst = false;
+            }
+            return true;
+        }
+
+        private Handler mHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case REDRAW:
+                        notifyDataSetChanged();
+                        break;
+                    case START_THREAD:
+                        startRequestProcessing();
+                        break;
+                }
+            }
+        };
+
+        public CallLogAdapter() {
+            super(getActivity());
+
+            mContactInfo = new HashMap<String,ContactInfo>();
+            mRequests = new LinkedList<CallerInfoQuery>();
+            mPreDrawListener = null;
+
+            mDrawableIncoming = getResources().getDrawable(
+                    R.drawable.ic_call_log_list_incoming_call);
+            mDrawableOutgoing = getResources().getDrawable(
+                    R.drawable.ic_call_log_list_outgoing_call);
+            mDrawableMissed = getResources().getDrawable(
+                    R.drawable.ic_call_log_list_missed_call);
+            mLabelArray = getResources().getTextArray(com.android.internal.R.array.phoneTypes);
+        }
+
+        /**
+         * Requery on background thread when {@link Cursor} changes.
+         */
+        @Override
+        protected void onContentChanged() {
+            // Start async requery
+            startQuery();
+        }
+
+        void setLoading(boolean loading) {
+            mLoading = loading;
+        }
+
+        @Override
+        public boolean isEmpty() {
+            if (mLoading) {
+                // We don't want the empty state to show when loading.
+                return false;
+            } else {
+                return super.isEmpty();
+            }
+        }
+
+        public ContactInfo getContactInfo(String number) {
+            return mContactInfo.get(number);
+        }
+
+        public void startRequestProcessing() {
+            mDone = false;
+            mCallerIdThread = new Thread(this);
+            mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
+            mCallerIdThread.start();
+        }
+
+        public void stopRequestProcessing() {
+            mDone = true;
+            if (mCallerIdThread != null) mCallerIdThread.interrupt();
+        }
+
+        public void clearCache() {
+            synchronized (mContactInfo) {
+                mContactInfo.clear();
+            }
+        }
+
+        private void updateCallLog(CallerInfoQuery ciq, ContactInfo ci) {
+            // Check if they are different. If not, don't update.
+            if (TextUtils.equals(ciq.name, ci.name)
+                    && TextUtils.equals(ciq.numberLabel, ci.label)
+                    && ciq.numberType == ci.type) {
+                return;
+            }
+            ContentValues values = new ContentValues(3);
+            values.put(Calls.CACHED_NAME, ci.name);
+            values.put(Calls.CACHED_NUMBER_TYPE, ci.type);
+            values.put(Calls.CACHED_NUMBER_LABEL, ci.label);
+
+            try {
+                getActivity().getContentResolver().update(Calls.CONTENT_URI, values,
+                        Calls.NUMBER + "='" + ciq.number + "'", null);
+            } catch (SQLiteDiskIOException e) {
+                Log.w(TAG, "Exception while updating call info", e);
+            } catch (SQLiteFullException e) {
+                Log.w(TAG, "Exception while updating call info", e);
+            } catch (SQLiteDatabaseCorruptException e) {
+                Log.w(TAG, "Exception while updating call info", e);
+            }
+        }
+
+        private void enqueueRequest(String number, int position,
+                String name, int numberType, String numberLabel) {
+            CallerInfoQuery ciq = new CallerInfoQuery();
+            ciq.number = number;
+            ciq.position = position;
+            ciq.name = name;
+            ciq.numberType = numberType;
+            ciq.numberLabel = numberLabel;
+            synchronized (mRequests) {
+                mRequests.add(ciq);
+                mRequests.notifyAll();
+            }
+        }
+
+        private boolean queryContactInfo(CallerInfoQuery ciq) {
+            // First check if there was a prior request for the same number
+            // that was already satisfied
+            ContactInfo info = mContactInfo.get(ciq.number);
+            boolean needNotify = false;
+            if (info != null && info != ContactInfo.EMPTY) {
+                return true;
+            } else {
+                // Ok, do a fresh Contacts lookup for ciq.number.
+                boolean infoUpdated = false;
+
+                if (PhoneNumberUtils.isUriNumber(ciq.number)) {
+                    // This "number" is really a SIP address.
+
+                    // TODO: This code is duplicated from the
+                    // CallerInfoAsyncQuery class.  To avoid that, could the
+                    // code here just use CallerInfoAsyncQuery, rather than
+                    // manually running ContentResolver.query() itself?
+
+                    // We look up SIP addresses directly in the Data table:
+                    Uri contactRef = Data.CONTENT_URI;
+
+                    // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
+                    //
+                    // Also note we use "upper(data1)" in the WHERE clause, and
+                    // uppercase the incoming SIP address, in order to do a
+                    // case-insensitive match.
+                    //
+                    // TODO: May also need to normalize by adding "sip:" as a
+                    // prefix, if we start storing SIP addresses that way in the
+                    // database.
+                    String selection = "upper(" + Data.DATA1 + ")=?"
+                            + " AND "
+                            + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'";
+                    String[] selectionArgs = new String[] { ciq.number.toUpperCase() };
+
+                    Cursor dataTableCursor =
+                            getActivity().getContentResolver().query(
+                                    contactRef,
+                                    null,  // projection
+                                    selection,  // selection
+                                    selectionArgs,  // selectionArgs
+                                    null);  // sortOrder
+
+                    if (dataTableCursor != null) {
+                        if (dataTableCursor.moveToFirst()) {
+                            info = new ContactInfo();
+
+                            // TODO: we could slightly speed this up using an
+                            // explicit projection (and thus not have to do
+                            // those getColumnIndex() calls) but the benefit is
+                            // very minimal.
+
+                            // Note the Data.CONTACT_ID column here is
+                            // equivalent to the PERSON_ID_COLUMN_INDEX column
+                            // we use with "phonesCursor" below.
+                            info.personId = dataTableCursor.getLong(
+                                    dataTableCursor.getColumnIndex(Data.CONTACT_ID));
+                            info.name = dataTableCursor.getString(
+                                    dataTableCursor.getColumnIndex(Data.DISPLAY_NAME));
+                            // "type" and "label" are currently unused for SIP addresses
+                            info.type = SipAddress.TYPE_OTHER;
+                            info.label = null;
+
+                            // And "number" is the SIP address.
+                            // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
+                            info.number = dataTableCursor.getString(
+                                    dataTableCursor.getColumnIndex(Data.DATA1));
+                            info.normalizedNumber = null;  // meaningless for SIP addresses
+
+                            infoUpdated = true;
+                        }
+                        dataTableCursor.close();
+                    }
+                } else {
+                    // "number" is a regular phone number, so use the
+                    // PhoneLookup table:
+                    Cursor phonesCursor =
+                            getActivity().getContentResolver().query(
+                                Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
+                                        Uri.encode(ciq.number)),
+                                        PhoneQuery._PROJECTION, null, null, null);
+                    if (phonesCursor != null) {
+                        if (phonesCursor.moveToFirst()) {
+                            info = new ContactInfo();
+                            info.personId = phonesCursor.getLong(PhoneQuery.PERSON_ID);
+                            info.name = phonesCursor.getString(PhoneQuery.NAME);
+                            info.type = phonesCursor.getInt(PhoneQuery.PHONE_TYPE);
+                            info.label = phonesCursor.getString(PhoneQuery.LABEL);
+                            info.number = phonesCursor
+                                    .getString(PhoneQuery.MATCHED_NUMBER);
+                            info.normalizedNumber = phonesCursor
+                                    .getString(PhoneQuery.NORMALIZED_NUMBER);
+
+                            infoUpdated = true;
+                        }
+                        phonesCursor.close();
+                    }
+                }
+
+                if (infoUpdated) {
+                    // New incoming phone number invalidates our formatted
+                    // cache. Any cache fills happen only on the GUI thread.
+                    info.formattedNumber = null;
+
+                    mContactInfo.put(ciq.number, info);
+
+                    // Inform list to update this item, if in view
+                    needNotify = true;
+                }
+            }
+            if (info != null) {
+                updateCallLog(ciq, info);
+            }
+            return needNotify;
+        }
+
+        /*
+         * Handles requests for contact name and number type
+         * @see java.lang.Runnable#run()
+         */
+        @Override
+        public void run() {
+            boolean needNotify = false;
+            while (!mDone) {
+                CallerInfoQuery ciq = null;
+                synchronized (mRequests) {
+                    if (!mRequests.isEmpty()) {
+                        ciq = mRequests.removeFirst();
+                    } else {
+                        if (needNotify) {
+                            needNotify = false;
+                            mHandler.sendEmptyMessage(REDRAW);
+                        }
+                        try {
+                            mRequests.wait(1000);
+                        } catch (InterruptedException ie) {
+                            // Ignore and continue processing requests
+                        }
+                    }
+                }
+                if (ciq != null && queryContactInfo(ciq)) {
+                    needNotify = true;
+                }
+            }
+        }
+
+        @Override
+        protected void addGroups(Cursor cursor) {
+
+            int count = cursor.getCount();
+            if (count == 0) {
+                return;
+            }
+
+            int groupItemCount = 1;
+
+            CharArrayBuffer currentValue = mBuffer1;
+            CharArrayBuffer value = mBuffer2;
+            cursor.moveToFirst();
+            cursor.copyStringToBuffer(CallLogQuery.NUMBER, currentValue);
+            int currentCallType = cursor.getInt(CallLogQuery.CALL_TYPE);
+            for (int i = 1; i < count; i++) {
+                cursor.moveToNext();
+                cursor.copyStringToBuffer(CallLogQuery.NUMBER, value);
+                boolean sameNumber = equalPhoneNumbers(value, currentValue);
+
+                // Group adjacent calls with the same number. Make an exception
+                // for the latest item if it was a missed call.  We don't want
+                // a missed call to be hidden inside a group.
+                if (sameNumber && currentCallType != Calls.MISSED_TYPE) {
+                    groupItemCount++;
+                } else {
+                    if (groupItemCount > 1) {
+                        addGroup(i - groupItemCount, groupItemCount, false);
+                    }
+
+                    groupItemCount = 1;
+
+                    // Swap buffers
+                    CharArrayBuffer temp = currentValue;
+                    currentValue = value;
+                    value = temp;
+
+                    // If we have just examined a row following a missed call, make
+                    // sure that it is grouped with subsequent calls from the same number
+                    // even if it was also missed.
+                    if (sameNumber && currentCallType == Calls.MISSED_TYPE) {
+                        currentCallType = 0;       // "not a missed call"
+                    } else {
+                        currentCallType = cursor.getInt(CallLogQuery.CALL_TYPE);
+                    }
+                }
+            }
+            if (groupItemCount > 1) {
+                addGroup(count - groupItemCount, groupItemCount, false);
+            }
+        }
+
+        protected boolean equalPhoneNumbers(CharArrayBuffer buffer1, CharArrayBuffer buffer2) {
+
+            // TODO add PhoneNumberUtils.compare(CharSequence, CharSequence) to avoid
+            // string allocation
+            return PhoneNumberUtils.compare(new String(buffer1.data, 0, buffer1.sizeCopied),
+                    new String(buffer2.data, 0, buffer2.sizeCopied));
+        }
+
+
+        @Override
+        public View newStandAloneView(Context context, ViewGroup parent) {
+            LayoutInflater inflater =
+                    (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
+            findAndCacheViews(view);
+            return view;
+        }
+
+        @Override
+        public void bindStandAloneView(View view, Context context, Cursor cursor) {
+            bindView(context, view, cursor);
+        }
+
+        @Override
+        protected View newChildView(Context context, ViewGroup parent) {
+            LayoutInflater inflater =
+                    (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            View view = inflater.inflate(R.layout.call_log_list_child_item, parent, false);
+            findAndCacheViews(view);
+            return view;
+        }
+
+        @Override
+        protected void bindChildView(View view, Context context, Cursor cursor) {
+            bindView(context, view, cursor);
+        }
+
+        @Override
+        protected View newGroupView(Context context, ViewGroup parent) {
+            LayoutInflater inflater =
+                    (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            View view = inflater.inflate(R.layout.call_log_list_group_item, parent, false);
+            findAndCacheViews(view);
+            return view;
+        }
+
+        @Override
+        protected void bindGroupView(View view, Context context, Cursor cursor, int groupSize,
+                boolean expanded) {
+            final CallLogListItemViews views = (CallLogListItemViews) view.getTag();
+            int groupIndicator = expanded
+                    ? com.android.internal.R.drawable.expander_ic_maximized
+                    : com.android.internal.R.drawable.expander_ic_minimized;
+            views.groupIndicator.setImageResource(groupIndicator);
+            views.groupSize.setText("(" + groupSize + ")");
+            bindView(context, view, cursor);
+        }
+
+        private void findAndCacheViews(View view) {
+
+            // Get the views to bind to
+            CallLogListItemViews views = new CallLogListItemViews();
+            views.line1View = (TextView) view.findViewById(R.id.line1);
+            views.labelView = (TextView) view.findViewById(R.id.label);
+            views.numberView = (TextView) view.findViewById(R.id.number);
+            views.dateView = (TextView) view.findViewById(R.id.date);
+            views.iconView = (ImageView) view.findViewById(R.id.call_type_icon);
+            views.callView = view.findViewById(R.id.call_icon);
+            views.callView.setOnClickListener(this);
+            views.groupIndicator = (ImageView) view.findViewById(R.id.groupIndicator);
+            views.groupSize = (TextView) view.findViewById(R.id.groupSize);
+            view.setTag(views);
+        }
+
+        public void bindView(Context context, View view, Cursor c) {
+            final CallLogListItemViews views = (CallLogListItemViews) view.getTag();
+
+            String number = c.getString(CallLogQuery.NUMBER);
+            String formattedNumber = null;
+            String callerName = c.getString(CallLogQuery.CALLER_NAME);
+            int callerNumberType = c.getInt(CallLogQuery.CALLER_NUMBERTYPE);
+            String callerNumberLabel = c.getString(CallLogQuery.CALLER_NUMBERLABEL);
+            String countryIso = c.getString(CallLogQuery.COUNTRY_ISO);
+            // Store away the number so we can call it directly if you click on the call icon
+            views.callView.setTag(number);
+
+            // Lookup contacts with this number
+            ContactInfo info = mContactInfo.get(number);
+            if (info == null) {
+                // Mark it as empty and queue up a request to find the name
+                // The db request should happen on a non-UI thread
+                info = ContactInfo.EMPTY;
+                mContactInfo.put(number, info);
+                enqueueRequest(number, c.getPosition(),
+                        callerName, callerNumberType, callerNumberLabel);
+            } else if (info != ContactInfo.EMPTY) { // Has been queried
+                // Check if any data is different from the data cached in the
+                // calls db. If so, queue the request so that we can update
+                // the calls db.
+                if (!TextUtils.equals(info.name, callerName)
+                        || info.type != callerNumberType
+                        || !TextUtils.equals(info.label, callerNumberLabel)) {
+                    // Something is amiss, so sync up.
+                    enqueueRequest(number, c.getPosition(),
+                            callerName, callerNumberType, callerNumberLabel);
+                }
+
+                // Format and cache phone number for found contact
+                if (info.formattedNumber == null) {
+                    info.formattedNumber =
+                            formatPhoneNumber(info.number, info.normalizedNumber, countryIso);
+                }
+                formattedNumber = info.formattedNumber;
+            }
+
+            String name = info.name;
+            int ntype = info.type;
+            String label = info.label;
+            // If there's no name cached in our hashmap, but there's one in the
+            // calls db, use the one in the calls db. Otherwise the name in our
+            // hashmap is more recent, so it has precedence.
+            if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(callerName)) {
+                name = callerName;
+                ntype = callerNumberType;
+                label = callerNumberLabel;
+
+                // Format the cached call_log phone number
+                formattedNumber = formatPhoneNumber(number, null, countryIso);
+            }
+            // Set the text lines and call icon.
+            // Assumes the call back feature is on most of the
+            // time. For private and unknown numbers: hide it.
+            views.callView.setVisibility(View.VISIBLE);
+
+            if (!TextUtils.isEmpty(name)) {
+                views.line1View.setText(name);
+                views.labelView.setVisibility(View.VISIBLE);
+
+                // "type" and "label" are currently unused for SIP addresses.
+                CharSequence numberLabel = null;
+                if (!PhoneNumberUtils.isUriNumber(number)) {
+                    numberLabel = Phone.getDisplayLabel(context, ntype, label,
+                            mLabelArray);
+                }
+                views.numberView.setVisibility(View.VISIBLE);
+                views.numberView.setText(formattedNumber);
+                if (!TextUtils.isEmpty(numberLabel)) {
+                    views.labelView.setText(numberLabel);
+                    views.labelView.setVisibility(View.VISIBLE);
+
+                    // Zero out the numberView's left margin (see below)
+                    ViewGroup.MarginLayoutParams numberLP =
+                            (ViewGroup.MarginLayoutParams) views.numberView.getLayoutParams();
+                    numberLP.leftMargin = 0;
+                    views.numberView.setLayoutParams(numberLP);
+                } else {
+                    // There's nothing to display in views.labelView, so hide it.
+                    // We can't set it to View.GONE, since it's the anchor for
+                    // numberView in the RelativeLayout, so make it INVISIBLE.
+                    //   Also, we need to manually *subtract* some left margin from
+                    // numberView to compensate for the right margin built in to
+                    // labelView (otherwise the number will be indented by a very
+                    // slight amount).
+                    //   TODO: a cleaner fix would be to contain both the label and
+                    // number inside a LinearLayout, and then set labelView *and*
+                    // its padding to GONE when there's no label to display.
+                    views.labelView.setText(null);
+                    views.labelView.setVisibility(View.INVISIBLE);
+
+                    ViewGroup.MarginLayoutParams labelLP =
+                            (ViewGroup.MarginLayoutParams) views.labelView.getLayoutParams();
+                    ViewGroup.MarginLayoutParams numberLP =
+                            (ViewGroup.MarginLayoutParams) views.numberView.getLayoutParams();
+                    // Equivalent to setting android:layout_marginLeft in XML
+                    numberLP.leftMargin = -labelLP.rightMargin;
+                    views.numberView.setLayoutParams(numberLP);
+                }
+            } else {
+                if (number.equals(CallerInfo.UNKNOWN_NUMBER)) {
+                    number = getString(R.string.unknown);
+                    views.callView.setVisibility(View.INVISIBLE);
+                } else if (number.equals(CallerInfo.PRIVATE_NUMBER)) {
+                    number = getString(R.string.private_num);
+                    views.callView.setVisibility(View.INVISIBLE);
+                } else if (number.equals(CallerInfo.PAYPHONE_NUMBER)) {
+                    number = getString(R.string.payphone);
+                } else if (PhoneNumberUtils.extractNetworkPortion(number)
+                                .equals(mVoiceMailNumber)) {
+                    number = getString(R.string.voicemail);
+                } else {
+                    // Just a raw number, and no cache, so format it nicely
+                    number = formatPhoneNumber(number, null, countryIso);
+                }
+
+                views.line1View.setText(number);
+                views.numberView.setVisibility(View.GONE);
+                views.labelView.setVisibility(View.GONE);
+            }
+
+            long date = c.getLong(CallLogQuery.DATE);
+
+            // Set the date/time field by mixing relative and absolute times.
+            int flags = DateUtils.FORMAT_ABBREV_RELATIVE;
+
+            views.dateView.setText(DateUtils.getRelativeTimeSpanString(date,
+                    System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, flags));
+
+            if (views.iconView != null) {
+                int type = c.getInt(CallLogQuery.CALL_TYPE);
+                // Set the icon
+                switch (type) {
+                    case Calls.INCOMING_TYPE:
+                        views.iconView.setImageDrawable(mDrawableIncoming);
+                        break;
+
+                    case Calls.OUTGOING_TYPE:
+                        views.iconView.setImageDrawable(mDrawableOutgoing);
+                        break;
+
+                    case Calls.MISSED_TYPE:
+                        views.iconView.setImageDrawable(mDrawableMissed);
+                        break;
+                }
+            }
+
+            // Listen for the first draw
+            if (mPreDrawListener == null) {
+                mFirst = true;
+                mPreDrawListener = this;
+                view.getViewTreeObserver().addOnPreDrawListener(this);
+            }
+        }
+    }
+
+    private static final class QueryHandler extends AsyncQueryHandler {
+        private final WeakReference<CallLogFragment> mFragment;
+
+        /**
+         * Simple handler that wraps background calls to catch
+         * {@link SQLiteException}, such as when the disk is full.
+         */
+        protected class CatchingWorkerHandler extends AsyncQueryHandler.WorkerHandler {
+            public CatchingWorkerHandler(Looper looper) {
+                super(looper);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                try {
+                    // Perform same query while catching any exceptions
+                    super.handleMessage(msg);
+                } catch (SQLiteDiskIOException e) {
+                    Log.w(TAG, "Exception on background worker thread", e);
+                } catch (SQLiteFullException e) {
+                    Log.w(TAG, "Exception on background worker thread", e);
+                } catch (SQLiteDatabaseCorruptException e) {
+                    Log.w(TAG, "Exception on background worker thread", e);
+                }
+            }
+        }
+
+        @Override
+        protected Handler createHandler(Looper looper) {
+            // Provide our special handler that catches exceptions
+            return new CatchingWorkerHandler(looper);
+        }
+
+        public QueryHandler(CallLogFragment fragment) {
+            super(fragment.getActivity().getContentResolver());
+            mFragment = new WeakReference<CallLogFragment>(fragment);
+        }
+
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+            final CallLogFragment fragment = mFragment.get();
+            // TODO: Do we still need the isFinishing check if we use a fragmented-Tabs?
+            if (fragment != null && !fragment.getActivity().isFinishing()) {
+                final CallLogFragment.CallLogAdapter callsAdapter = fragment.mAdapter;
+                callsAdapter.setLoading(false);
+                callsAdapter.changeCursor(cursor);
+                if (fragment.mScrollToTop) {
+                    final ListView listView = fragment.getListView();
+                    if (listView.getFirstVisiblePosition() > 5) {
+                        listView.setSelection(5);
+                    }
+                    listView.smoothScrollToPosition(0);
+                    fragment.mScrollToTop = false;
+                }
+            } else {
+                cursor.close();
+            }
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle state) {
+        super.onCreate(state);
+
+        mVoiceMailNumber = ((TelephonyManager) getActivity().getSystemService(
+                Context.TELEPHONY_SERVICE)).getVoiceMailNumber();
+        mQueryHandler = new QueryHandler(this);
+
+        mCurrentCountryIso = ContactsUtils.getCurrentCountryIso(getActivity());
+
+        setHasOptionsMenu(true);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+        return inflater.inflate(R.layout.call_log_fragment, container, false);
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        getListView().setOnCreateContextMenuListener(this);
+        mAdapter = new CallLogAdapter();
+        setListAdapter(mAdapter);
+    }
+
+    @Override
+    public void onStart() {
+        mScrollToTop = true;
+        super.onStart();
+    }
+
+    @Override
+    public void onResume() {
+        // The adapter caches looked up numbers, clear it so they will get
+        // looked up again.
+        if (mAdapter != null) {
+            mAdapter.clearCache();
+        }
+
+        startQuery();
+        resetNewCallsFlag();
+
+        super.onResume();
+
+        mAdapter.mPreDrawListener = null; // Let it restart the thread after next draw
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        // Kill the requests thread
+        mAdapter.stopRequestProcessing();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mAdapter.stopRequestProcessing();
+        mAdapter.changeCursor(null);
+    }
+
+    /**
+     * Format the given phone number
+     *
+     * @param number the number to be formatted.
+     * @param normalizedNumber the normalized number of the given number.
+     * @param countryIso the ISO 3166-1 two letters country code, the country's
+     *        convention will be used to format the number if the normalized
+     *        phone is null.
+     *
+     * @return the formatted number, or the given number if it was formatted.
+     */
+    private String formatPhoneNumber(String number, String normalizedNumber, String countryIso) {
+        if (TextUtils.isEmpty(number)) {
+            return "";
+        }
+        // If "number" is really a SIP address, don't try to do any formatting at all.
+        if (PhoneNumberUtils.isUriNumber(number)) {
+            return number;
+        }
+        if (TextUtils.isEmpty(countryIso)) {
+            countryIso = mCurrentCountryIso;
+        }
+        return PhoneNumberUtils.formatNumber(number, normalizedNumber, countryIso);
+    }
+
+    private void resetNewCallsFlag() {
+        // Mark all "new" missed calls as not new anymore
+        StringBuilder where = new StringBuilder("type=");
+        where.append(Calls.MISSED_TYPE);
+        where.append(" AND new=1");
+
+        ContentValues values = new ContentValues(1);
+        values.put(Calls.NEW, "0");
+        mQueryHandler.startUpdate(UPDATE_TOKEN, null, Calls.CONTENT_URI,
+                values, where.toString(), null);
+    }
+
+    private void startQuery() {
+        mAdapter.setLoading(true);
+
+        // Cancel any pending queries
+        mQueryHandler.cancelOperation(QUERY_TOKEN);
+        mQueryHandler.startQuery(QUERY_TOKEN, null, Calls.CONTENT_URI,
+                CallLogQuery._PROJECTION, null, null, Calls.DEFAULT_SORT_ORDER);
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+        menu.add(0, OptionsMenuItems.DELETE_ALL, 0, R.string.recentCalls_deleteAll).setIcon(
+                android.R.drawable.ic_menu_close_clear_cancel);
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
+        AdapterView.AdapterContextMenuInfo menuInfo;
+        try {
+             menuInfo = (AdapterView.AdapterContextMenuInfo) menuInfoIn;
+        } catch (ClassCastException e) {
+            Log.e(TAG, "bad menuInfoIn", e);
+            return;
+        }
+
+        Cursor cursor = (Cursor) mAdapter.getItem(menuInfo.position);
+
+        String number = cursor.getString(CallLogQuery.NUMBER);
+        Uri numberUri = null;
+        boolean isVoicemail = false;
+        boolean isSipNumber = false;
+        if (number.equals(CallerInfo.UNKNOWN_NUMBER)) {
+            number = getString(R.string.unknown);
+        } else if (number.equals(CallerInfo.PRIVATE_NUMBER)) {
+            number = getString(R.string.private_num);
+        } else if (number.equals(CallerInfo.PAYPHONE_NUMBER)) {
+            number = getString(R.string.payphone);
+        } else if (PhoneNumberUtils.extractNetworkPortion(number).equals(mVoiceMailNumber)) {
+            number = getString(R.string.voicemail);
+            numberUri = Uri.parse("voicemail:x");
+            isVoicemail = true;
+        } else if (PhoneNumberUtils.isUriNumber(number)) {
+            numberUri = Uri.fromParts("sip", number, null);
+            isSipNumber = true;
+        } else {
+            numberUri = Uri.fromParts("tel", number, null);
+        }
+
+        ContactInfo info = mAdapter.getContactInfo(number);
+        boolean contactInfoPresent = (info != null && info != ContactInfo.EMPTY);
+        if (contactInfoPresent) {
+            menu.setHeaderTitle(info.name);
+        } else {
+            menu.setHeaderTitle(number);
+        }
+
+        if (numberUri != null) {
+            Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, numberUri);
+            menu.add(0, 0, 0, getResources().getString(R.string.recentCalls_callNumber, number))
+                    .setIntent(intent);
+        }
+
+        if (contactInfoPresent) {
+            menu.add(0, 0, 0, R.string.menu_viewContact)
+                    .setIntent(new Intent(Intent.ACTION_VIEW,
+                            ContentUris.withAppendedId(Contacts.CONTENT_URI, info.personId)));
+        }
+
+        if (numberUri != null && !isVoicemail && !isSipNumber) {
+            menu.add(0, 0, 0, R.string.recentCalls_editNumberBeforeCall)
+                    .setIntent(new Intent(Intent.ACTION_DIAL, numberUri));
+            menu.add(0, 0, 0, R.string.menu_sendTextMessage)
+                    .setIntent(new Intent(Intent.ACTION_SENDTO,
+                            Uri.fromParts("sms", number, null)));
+        }
+
+        // "Add to contacts" item, if this entry isn't already associated with a contact
+        if (!contactInfoPresent && numberUri != null && !isVoicemail && !isSipNumber) {
+            // TODO: This item is currently disabled for SIP addresses, because
+            // the Insert.PHONE extra only works correctly for PSTN numbers.
+            //
+            // To fix this for SIP addresses, we need to:
+            // - define ContactsContract.Intents.Insert.SIP_ADDRESS, and use it here if
+            //   the current number is a SIP address
+            // - update the contacts UI code to handle Insert.SIP_ADDRESS by
+            //   updating the SipAddress field
+            // and then we can remove the "!isSipNumber" check above.
+
+            Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+            intent.setType(Contacts.CONTENT_ITEM_TYPE);
+            intent.putExtra(Insert.PHONE, number);
+            menu.add(0, 0, 0, R.string.recentCalls_addToContact)
+                    .setIntent(intent);
+        }
+        menu.add(0, MenuItems.DELETE, 0, R.string.recentCalls_removeFromRecentList);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case OptionsMenuItems.DELETE_ALL: {
+                ClearCallLogDialog.show(getFragmentManager());
+                return true;
+            }
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        // Convert the menu info to the proper type
+        AdapterView.AdapterContextMenuInfo menuInfo;
+        try {
+             menuInfo = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
+        } catch (ClassCastException e) {
+            Log.e(TAG, "bad menuInfoIn", e);
+            return false;
+        }
+
+        switch (item.getItemId()) {
+            case MenuItems.DELETE: {
+                Cursor cursor = (Cursor)mAdapter.getItem(menuInfo.position);
+                int groupSize = 1;
+                if (mAdapter.isGroupHeader(menuInfo.position)) {
+                    groupSize = mAdapter.getGroupSize(menuInfo.position);
+                }
+
+                StringBuilder sb = new StringBuilder();
+                for (int i = 0; i < groupSize; i++) {
+                    if (i != 0) {
+                        sb.append(",");
+                        cursor.moveToNext();
+                    }
+                    long id = cursor.getLong(CallLogQuery.ID);
+                    sb.append(id);
+                }
+
+                getActivity().getContentResolver().delete(Calls.CONTENT_URI,
+                        Calls._ID + " IN (" + sb + ")", null);
+            }
+        }
+        return super.onContextItemSelected(item);
+    }
+
+    /*
+     * Get the number from the Contacts, if available, since sometimes
+     * the number provided by caller id may not be formatted properly
+     * depending on the carrier (roaming) in use at the time of the
+     * incoming call.
+     * Logic : If the caller-id number starts with a "+", use it
+     *         Else if the number in the contacts starts with a "+", use that one
+     *         Else if the number in the contacts is longer, use that one
+     */
+    private String getBetterNumberFromContacts(String number) {
+        String matchingNumber = null;
+        // Look in the cache first. If it's not found then query the Phones db
+        ContactInfo ci = mAdapter.mContactInfo.get(number);
+        if (ci != null && ci != ContactInfo.EMPTY) {
+            matchingNumber = ci.number;
+        } else {
+            try {
+                Cursor phonesCursor = getActivity().getContentResolver().query(
+                        Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, number),
+                        PhoneQuery._PROJECTION, null, null, null);
+                if (phonesCursor != null) {
+                    if (phonesCursor.moveToFirst()) {
+                        matchingNumber = phonesCursor.getString(PhoneQuery.MATCHED_NUMBER);
+                    }
+                    phonesCursor.close();
+                }
+            } catch (Exception e) {
+                // Use the number from the call log
+            }
+        }
+        if (!TextUtils.isEmpty(matchingNumber) &&
+                (matchingNumber.startsWith("+")
+                        || matchingNumber.length() > number.length())) {
+            number = matchingNumber;
+        }
+        return number;
+    }
+
+    public void callSelectedEntry() {
+        int position = getListView().getSelectedItemPosition();
+        if (position < 0) {
+            // In touch mode you may often not have something selected, so
+            // just call the first entry to make sure that [send] [send] calls the
+            // most recent entry.
+            position = 0;
+        }
+        final Cursor cursor = (Cursor)mAdapter.getItem(position);
+        if (cursor != null) {
+            String number = cursor.getString(CallLogQuery.NUMBER);
+            if (TextUtils.isEmpty(number)
+                    || number.equals(CallerInfo.UNKNOWN_NUMBER)
+                    || number.equals(CallerInfo.PRIVATE_NUMBER)
+                    || number.equals(CallerInfo.PAYPHONE_NUMBER)) {
+                // This number can't be called, do nothing
+                return;
+            }
+            Intent intent;
+            // If "number" is really a SIP address, construct a sip: URI.
+            if (PhoneNumberUtils.isUriNumber(number)) {
+                intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
+                                    Uri.fromParts("sip", number, null));
+            } else {
+                // We're calling a regular PSTN phone number.
+                // Construct a tel: URI, but do some other possible cleanup first.
+                int callType = cursor.getInt(CallLogQuery.CALL_TYPE);
+                if (!number.startsWith("+") &&
+                       (callType == Calls.INCOMING_TYPE
+                                || callType == Calls.MISSED_TYPE)) {
+                    // If the caller-id matches a contact with a better qualified number, use it
+                    number = getBetterNumberFromContacts(number);
+                }
+                intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
+                                    Uri.fromParts("tel", number, null));
+            }
+            intent.setFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+            startActivity(intent);
+        }
+    }
+
+    @Override
+    public void onListItemClick(ListView l, View v, int position, long id) {
+        if (mAdapter.isGroupHeader(position)) {
+            mAdapter.toggleGroup(position);
+        } else {
+            Intent intent = new Intent(getActivity(), CallDetailActivity.class);
+            intent.setData(ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI, id));
+            startActivity(intent);
+        }
+    }
+
+    public CallLogAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    public String getVoiceMailNumber() {
+        return mVoiceMailNumber;
+    }
+}
diff --git a/src/com/android/contacts/calllog/ClearCallLogDialog.java b/src/com/android/contacts/calllog/ClearCallLogDialog.java
new file mode 100644
index 0000000..426732a
--- /dev/null
+++ b/src/com/android/contacts/calllog/ClearCallLogDialog.java
@@ -0,0 +1,78 @@
+/*
+ * 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 android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.app.ProgressDialog;
+import android.content.ContentResolver;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.CallLog.Calls;
+
+/**
+ * Dialog that clears the call log after confirming with the user
+ */
+public class ClearCallLogDialog extends DialogFragment {
+    /** Preferred way to show this dialog */
+    public static void show(FragmentManager fragmentManager) {
+        ClearCallLogDialog dialog = new ClearCallLogDialog();
+        dialog.show(fragmentManager, "deleteCallLog");
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        final ContentResolver resolver = getActivity().getContentResolver();
+        final OnClickListener okListener = new OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                final ProgressDialog progressDialog = ProgressDialog.show(getActivity(),
+                        getString(R.string.clearCallLogProgress_title),
+                        "", true, false);
+                final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+                    @Override
+                    protected Void doInBackground(Void... params) {
+                        resolver.delete(Calls.CONTENT_URI, null, null);
+                        return null;
+                    }
+                    @Override
+                    protected void onPostExecute(Void result) {
+                        progressDialog.dismiss();
+                    }
+                };
+                // TODO: Once we have the API, we should configure this ProgressDialog
+                // to only show up after a certain time (e.g. 150ms)
+                progressDialog.show();
+                task.execute();
+            }
+        };
+        return new AlertDialog.Builder(getActivity())
+            .setTitle(R.string.clearCallLogConfirmation_title)
+            .setIconAttribute(android.R.attr.alertDialogIcon)
+            .setMessage(R.string.clearCallLogConfirmation)
+            .setNegativeButton(android.R.string.cancel, null)
+            .setPositiveButton(android.R.string.ok, okListener)
+            .setCancelable(true)
+            .create();
+    }
+}
diff --git a/tests/src/com/android/contacts/activities/CallLogActivityTests.java b/tests/src/com/android/contacts/activities/CallLogActivityTests.java
index c6b3ea5..3eedef0 100644
--- a/tests/src/com/android/contacts/activities/CallLogActivityTests.java
+++ b/tests/src/com/android/contacts/activities/CallLogActivityTests.java
@@ -16,6 +16,7 @@
 
 package com.android.contacts.activities;
 
+import com.android.contacts.calllog.CallLogFragment;
 import com.android.internal.telephony.CallerInfo;
 
 import android.content.res.Resources;
@@ -66,8 +67,9 @@
     // CallLogActivity to build the rows (view) in the call
     // list. We reuse it with our own in-mem DB.
     private CallLogActivity mActivity;
+    private CallLogFragment mFragment;
     private FrameLayout mParentView;
-    private CallLogActivity.CallLogAdapter mAdapter;
+    private CallLogFragment.CallLogAdapter mAdapter;
     private String mVoicemail;
 
     // In memory array to hold the rows corresponding to the 'calls' table.
@@ -82,7 +84,7 @@
     private HashMap<Integer, Bitmap> mCallTypeIcons;
 
     // An item in the call list. All the methods performing checks use it.
-    private CallLogActivity.CallLogListItemViews mItem;
+    private CallLogFragment.CallLogListItemViews mItem;
     // The list of views representing the data in the DB. View are in
     // reverse order compare to the DB.
     private View[] mList;
@@ -96,8 +98,9 @@
     @Override
     public void setUp() {
         mActivity = getActivity();
-        mVoicemail = mActivity.mVoiceMailNumber;
-        mAdapter = mActivity.mAdapter;
+        mFragment = mActivity.getFragment();
+        mVoicemail = mFragment.getVoiceMailNumber();
+        mAdapter = mFragment.getAdapter();
         mParentView = new FrameLayout(mActivity);
         mCursor = new MatrixCursor(CALL_LOG_PROJECTION);
         buildIconMap();
@@ -167,7 +170,7 @@
             if (null == mList[i]) {
                 break;
             }
-            mItem = (CallLogActivity.CallLogListItemViews) mList[i].getTag();
+            mItem = (CallLogFragment.CallLogListItemViews) mList[i].getTag();
 
             // callView tag is the phone number.
             String number = (String) mItem.callView.getTag();