Moving more classes from contacts into dialer.

- These classes are only used by dialer code.
- Fixed import order.

Bug: 6993891
Change-Id: I7941a029989c4793b766fdc77a4666f9f99b750a
diff --git a/src/com/android/dialer/BackScrollManager.java b/src/com/android/dialer/BackScrollManager.java
new file mode 100644
index 0000000..5728702
--- /dev/null
+++ b/src/com/android/dialer/BackScrollManager.java
@@ -0,0 +1,89 @@
+/*
+ * 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.dialer;
+
+import android.view.View;
+import android.widget.AbsListView;
+import android.widget.ListView;
+
+/**
+ * Handles scrolling back of a list tied to a header.
+ * <p>
+ * This is used to implement a header that scrolls up with the content of a list to be partially
+ * obscured.
+ */
+public class BackScrollManager {
+    /** Defines the header to be scrolled. */
+    public interface ScrollableHeader {
+        /** Sets the offset by which to scroll. */
+        public void setOffset(int offset);
+        /** Gets the maximum offset that should be applied to the header. */
+        public int getMaximumScrollableHeaderOffset();
+    }
+
+    private final ScrollableHeader mHeader;
+    private final ListView mListView;
+
+    private final AbsListView.OnScrollListener mScrollListener =
+            new AbsListView.OnScrollListener() {
+                @Override
+                public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+                        int totalItemCount) {
+                    if (firstVisibleItem != 0) {
+                        // The first item is not shown, the header should be pinned at the top.
+                        mHeader.setOffset(mHeader.getMaximumScrollableHeaderOffset());
+                        return;
+                    }
+
+                    View firstVisibleItemView = view.getChildAt(firstVisibleItem);
+                    if (firstVisibleItemView == null) {
+                        return;
+                    }
+                    // We scroll the header up, but at most pin it to the top of the screen.
+                    int offset = Math.min(
+                            (int) -view.getChildAt(firstVisibleItem).getY(),
+                            mHeader.getMaximumScrollableHeaderOffset());
+                    mHeader.setOffset(offset);
+                }
+
+                @Override
+                public void onScrollStateChanged(AbsListView view, int scrollState) {
+                    // Nothing to do here.
+                }
+            };
+
+    /**
+     * Creates a new instance of a {@link BackScrollManager} that connected the header and the list
+     * view.
+     */
+    public static void bind(ScrollableHeader header, ListView listView) {
+        BackScrollManager backScrollManager = new BackScrollManager(header, listView);
+        backScrollManager.bind();
+    }
+
+    private BackScrollManager(ScrollableHeader header, ListView listView) {
+        mHeader = header;
+        mListView = listView;
+    }
+
+    private void bind() {
+        mListView.setOnScrollListener(mScrollListener);
+        // We disable the scroll bar because it would otherwise be incorrect because of the hidden
+        // header.
+        mListView.setVerticalScrollBarEnabled(false);
+    }
+}
diff --git a/src/com/android/dialer/CallDetailActivity.java b/src/com/android/dialer/CallDetailActivity.java
index f4ca213..cb1437d 100644
--- a/src/com/android/dialer/CallDetailActivity.java
+++ b/src/com/android/dialer/CallDetailActivity.java
@@ -51,23 +51,20 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
-import com.android.contacts.BackScrollManager;
-import com.android.contacts.BackScrollManager.ScrollableHeader;
 import com.android.contacts.ContactPhotoManager;
 import com.android.contacts.ContactsUtils;
-import com.android.contacts.ProximitySensorAware;
-import com.android.contacts.ProximitySensorManager;
 import com.android.contacts.R;
+import com.android.contacts.format.FormatUtils;
+import com.android.contacts.util.ClipboardUtils;
+import com.android.contacts.util.Constants;
+import com.android.dialer.BackScrollManager.ScrollableHeader;
 import com.android.dialer.calllog.CallDetailHistoryAdapter;
 import com.android.dialer.calllog.CallTypeHelper;
 import com.android.dialer.calllog.ContactInfo;
 import com.android.dialer.calllog.ContactInfoHelper;
 import com.android.dialer.calllog.PhoneNumberHelper;
-import com.android.contacts.format.FormatUtils;
-import com.android.contacts.util.AsyncTaskExecutor;
-import com.android.contacts.util.AsyncTaskExecutors;
-import com.android.contacts.util.ClipboardUtils;
-import com.android.contacts.util.Constants;
+import com.android.dialer.util.AsyncTaskExecutor;
+import com.android.dialer.util.AsyncTaskExecutors;
 import com.android.dialer.voicemail.VoicemailPlaybackFragment;
 import com.android.dialer.voicemail.VoicemailStatusHelper;
 import com.android.dialer.voicemail.VoicemailStatusHelper.StatusMessage;
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index 380b265..841db1c 100644
--- a/src/com/android/dialer/DialtactsActivity.java
+++ b/src/com/android/dialer/DialtactsActivity.java
@@ -59,17 +59,17 @@
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
 import com.android.contacts.activities.TransactionSafeActivity;
-import com.android.dialer.calllog.CallLogFragment;
-import com.android.dialer.dialpad.DialpadFragment;
 import com.android.contacts.interactions.PhoneNumberInteraction;
 import com.android.contacts.list.ContactListFilterController;
 import com.android.contacts.list.ContactListFilterController.ContactListFilterListener;
 import com.android.contacts.list.ContactListItemView;
 import com.android.contacts.list.OnPhoneNumberPickerActionListener;
-import com.android.contacts.list.PhoneFavoriteFragment;
 import com.android.contacts.list.PhoneNumberPickerFragment;
 import com.android.contacts.util.AccountFilterUtil;
 import com.android.contacts.util.Constants;
+import com.android.dialer.calllog.CallLogFragment;
+import com.android.dialer.dialpad.DialpadFragment;
+import com.android.dialer.list.PhoneFavoriteFragment;
 import com.android.internal.telephony.ITelephony;
 
 /**
diff --git a/src/com/android/dialer/PhoneCallDetailsHelper.java b/src/com/android/dialer/PhoneCallDetailsHelper.java
index 8433ebc..e1420d6 100644
--- a/src/com/android/dialer/PhoneCallDetailsHelper.java
+++ b/src/com/android/dialer/PhoneCallDetailsHelper.java
@@ -30,9 +30,9 @@
 import android.widget.TextView;
 
 import com.android.contacts.R;
+import com.android.contacts.test.NeededForTesting;
 import com.android.dialer.calllog.CallTypeHelper;
 import com.android.dialer.calllog.PhoneNumberHelper;
-import com.android.contacts.test.NeededForTesting;
 
 /**
  * Helper class to fill in the views in {@link PhoneCallDetailsViews}.
diff --git a/src/com/android/dialer/ProximitySensorAware.java b/src/com/android/dialer/ProximitySensorAware.java
new file mode 100644
index 0000000..145b860
--- /dev/null
+++ b/src/com/android/dialer/ProximitySensorAware.java
@@ -0,0 +1,33 @@
+/*
+ * 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.dialer;
+
+/**
+ * An object that is aware of the state of the proximity sensor.
+ */
+public interface ProximitySensorAware {
+    /** Start tracking the state of the proximity sensor. */
+    public void enableProximitySensor();
+
+    /**
+     * Stop tracking the state of the proximity sensor.
+     *
+     * @param waitForFarState if true and the sensor is currently in the near state, it will wait
+     *         until it is again in the far state before stopping to track its state.
+     */
+    public void disableProximitySensor(boolean waitForFarState);
+}
diff --git a/src/com/android/dialer/ProximitySensorManager.java b/src/com/android/dialer/ProximitySensorManager.java
new file mode 100644
index 0000000..42d740f
--- /dev/null
+++ b/src/com/android/dialer/ProximitySensorManager.java
@@ -0,0 +1,237 @@
+/*
+ * 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.dialer;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Manages the proximity sensor and notifies a listener when enabled.
+ */
+public class ProximitySensorManager {
+    /**
+     * Listener of the state of the proximity sensor.
+     * <p>
+     * This interface abstracts two possible states for the proximity sensor, near and far.
+     * <p>
+     * The actual meaning of these states depends on the actual sensor.
+     */
+    public interface Listener {
+        /** Called when the proximity sensor transitions from the far to the near state. */
+        public void onNear();
+        /** Called when the proximity sensor transitions from the near to the far state. */
+        public void onFar();
+    }
+
+    public static enum State {
+        NEAR, FAR
+    }
+
+    private final ProximitySensorEventListener mProximitySensorListener;
+
+    /**
+     * The current state of the manager, i.e., whether it is currently tracking the state of the
+     * sensor.
+     */
+    private boolean mManagerEnabled;
+
+    /**
+     * The listener to the state of the sensor.
+     * <p>
+     * Contains most of the logic concerning tracking of the sensor.
+     * <p>
+     * After creating an instance of this object, one should call {@link #register()} and
+     * {@link #unregister()} to enable and disable the notifications.
+     * <p>
+     * Instead of calling unregister, one can call {@link #unregisterWhenFar()} to unregister the
+     * listener the next time the sensor reaches the {@link State#FAR} state if currently in the
+     * {@link State#NEAR} state.
+     */
+    private static class ProximitySensorEventListener implements SensorEventListener {
+        private static final float FAR_THRESHOLD = 5.0f;
+
+        private final SensorManager mSensorManager;
+        private final Sensor mProximitySensor;
+        private final float mMaxValue;
+        private final Listener mListener;
+
+        /**
+         * The last state of the sensor.
+         * <p>
+         * Before registering and after unregistering we are always in the {@link State#FAR} state.
+         */
+        @GuardedBy("this") private State mLastState;
+        /**
+         * If this flag is set to true, we are waiting to reach the {@link State#FAR} state and
+         * should notify the listener and unregister when that happens.
+         */
+        @GuardedBy("this") private boolean mWaitingForFarState;
+
+        public ProximitySensorEventListener(SensorManager sensorManager, Sensor proximitySensor,
+                Listener listener) {
+            mSensorManager = sensorManager;
+            mProximitySensor = proximitySensor;
+            mMaxValue = proximitySensor.getMaximumRange();
+            mListener = listener;
+            // Initialize at far state.
+            mLastState = State.FAR;
+            mWaitingForFarState = false;
+        }
+
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            // Make sure we have a valid value.
+            if (event.values == null) return;
+            if (event.values.length == 0) return;
+            float value = event.values[0];
+            // Convert the sensor into a NEAR/FAR state.
+            State state = getStateFromValue(value);
+            synchronized (this) {
+                // No change in state, do nothing.
+                if (state == mLastState) return;
+                // Keep track of the current state.
+                mLastState = state;
+                // If we are waiting to reach the far state and we are now in it, unregister.
+                if (mWaitingForFarState && mLastState == State.FAR) {
+                    unregisterWithoutNotification();
+                }
+            }
+            // Notify the listener of the state change.
+            switch (state) {
+                case NEAR:
+                    mListener.onNear();
+                    break;
+
+                case FAR:
+                    mListener.onFar();
+                    break;
+            }
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+            // Nothing to do here.
+        }
+
+        /** Returns the state of the sensor given its current value. */
+        private State getStateFromValue(float value) {
+            // Determine if the current value corresponds to the NEAR or FAR state.
+            // Take case of the case where the proximity sensor is binary: if the current value is
+            // equal to the maximum, we are always in the FAR state.
+            return (value > FAR_THRESHOLD || value == mMaxValue) ? State.FAR : State.NEAR;
+        }
+
+        /**
+         * Unregister the next time the sensor reaches the {@link State#FAR} state.
+         */
+        public synchronized void unregisterWhenFar() {
+            if (mLastState == State.FAR) {
+                // We are already in the far state, just unregister now.
+                unregisterWithoutNotification();
+            } else {
+                mWaitingForFarState = true;
+            }
+        }
+
+        /** Register the listener and call the listener as necessary. */
+        public synchronized void register() {
+            // It is okay to register multiple times.
+            mSensorManager.registerListener(this, mProximitySensor, SensorManager.SENSOR_DELAY_UI);
+            // We should no longer be waiting for the far state if we are registering again.
+            mWaitingForFarState = false;
+        }
+
+        public void unregister() {
+            State lastState;
+            synchronized (this) {
+                unregisterWithoutNotification();
+                lastState = mLastState;
+                // Always go back to the FAR state. That way, when we register again we will get a
+                // transition when the sensor gets into the NEAR state.
+                mLastState = State.FAR;
+            }
+            // Notify the listener if we changed the state to FAR while unregistering.
+            if (lastState != State.FAR) {
+                mListener.onFar();
+            }
+        }
+
+        @GuardedBy("this")
+        private void unregisterWithoutNotification() {
+            mSensorManager.unregisterListener(this);
+            mWaitingForFarState = false;
+        }
+    }
+
+    public ProximitySensorManager(Context context, Listener listener) {
+        SensorManager sensorManager =
+                (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+        Sensor proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+        if (proximitySensor == null) {
+            // If there is no sensor, we should not do anything.
+            mProximitySensorListener = null;
+        } else {
+            mProximitySensorListener =
+                    new ProximitySensorEventListener(sensorManager, proximitySensor, listener);
+        }
+    }
+
+    /**
+     * Enables the proximity manager.
+     * <p>
+     * The listener will start getting notifications of events.
+     * <p>
+     * This method is idempotent.
+     */
+    public void enable() {
+        if (mProximitySensorListener != null && !mManagerEnabled) {
+            mProximitySensorListener.register();
+            mManagerEnabled = true;
+        }
+    }
+
+    /**
+     * Disables the proximity manager.
+     * <p>
+     * The listener will stop receiving notifications of events, possibly after receiving a last
+     * {@link Listener#onFar()} callback.
+     * <p>
+     * If {@code waitForFarState} is true, if the sensor is not currently in the {@link State#FAR}
+     * state, the listener will receive a {@link Listener#onFar()} callback the next time the sensor
+     * actually reaches the {@link State#FAR} state.
+     * <p>
+     * If {@code waitForFarState} is false, the listener will receive a {@link Listener#onFar()}
+     * callback immediately if the sensor is currently not in the {@link State#FAR} state.
+     * <p>
+     * This method is idempotent.
+     */
+    public void disable(boolean waitForFarState) {
+        if (mProximitySensorListener != null && mManagerEnabled) {
+            if (waitForFarState) {
+                mProximitySensorListener.unregisterWhenFar();
+            } else {
+                mProximitySensorListener.unregister();
+            }
+            mManagerEnabled = false;
+        }
+    }
+}
diff --git a/src/com/android/dialer/SpecialCharSequenceMgr.java b/src/com/android/dialer/SpecialCharSequenceMgr.java
new file mode 100644
index 0000000..5b88c8d
--- /dev/null
+++ b/src/com/android/dialer/SpecialCharSequenceMgr.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2006 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.dialer;
+
+import android.app.AlertDialog;
+import android.app.KeyguardManager;
+import android.app.ProgressDialog;
+import android.content.AsyncQueryHandler;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.WindowManager;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import com.android.contacts.R;
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.TelephonyCapabilities;
+import com.android.internal.telephony.TelephonyIntents;
+
+/**
+ * Helper class to listen for some magic character sequences
+ * that are handled specially by the dialer.
+ *
+ * Note the Phone app also handles these sequences too (in a couple of
+ * relativly obscure places in the UI), so there's a separate version of
+ * this class under apps/Phone.
+ *
+ * TODO: there's lots of duplicated code between this class and the
+ * corresponding class under apps/Phone.  Let's figure out a way to
+ * unify these two classes (in the framework? in a common shared library?)
+ */
+public class SpecialCharSequenceMgr {
+    private static final String TAG = "SpecialCharSequenceMgr";
+    private static final String MMI_IMEI_DISPLAY = "*#06#";
+
+    /**
+     * Remembers the previous {@link QueryHandler} and cancel the operation when needed, to
+     * prevent possible crash.
+     *
+     * QueryHandler may call {@link ProgressDialog#dismiss()} when the screen is already gone,
+     * which will cause the app crash. This variable enables the class to prevent the crash
+     * on {@link #cleanup()}.
+     *
+     * TODO: Remove this and replace it (and {@link #cleanup()}) with better implementation.
+     * One complication is that we have SpecialCharSequencMgr in Phone package too, which has
+     * *slightly* different implementation. Note that Phone package doesn't have this problem,
+     * so the class on Phone side doesn't have this functionality.
+     * Fundamental fix would be to have one shared implementation and resolve this corner case more
+     * gracefully.
+     */
+    private static QueryHandler sPreviousAdnQueryHandler;
+
+    /** This class is never instantiated. */
+    private SpecialCharSequenceMgr() {
+    }
+
+    public static boolean handleChars(Context context, String input, EditText textField) {
+        return handleChars(context, input, false, textField);
+    }
+
+    static boolean handleChars(Context context, String input) {
+        return handleChars(context, input, false, null);
+    }
+
+    static boolean handleChars(Context context, String input, boolean useSystemWindow,
+            EditText textField) {
+
+        //get rid of the separators so that the string gets parsed correctly
+        String dialString = PhoneNumberUtils.stripSeparators(input);
+
+        if (handleIMEIDisplay(context, dialString, useSystemWindow)
+                || handlePinEntry(context, dialString)
+                || handleAdnEntry(context, dialString, textField)
+                || handleSecretCode(context, dialString)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Cleanup everything around this class. Must be run inside the main thread.
+     *
+     * This should be called when the screen becomes background.
+     */
+    public static void cleanup() {
+        if (Looper.myLooper() != Looper.getMainLooper()) {
+            Log.wtf(TAG, "cleanup() is called outside the main thread");
+            return;
+        }
+
+        if (sPreviousAdnQueryHandler != null) {
+            sPreviousAdnQueryHandler.cancel();
+            sPreviousAdnQueryHandler = null;
+        }
+    }
+
+    /**
+     * Handles secret codes to launch arbitrary activities in the form of *#*#<code>#*#*.
+     * If a secret code is encountered an Intent is started with the android_secret_code://<code>
+     * URI.
+     *
+     * @param context the context to use
+     * @param input the text to check for a secret code in
+     * @return true if a secret code was encountered
+     */
+    static boolean handleSecretCode(Context context, String input) {
+        // Secret codes are in the form *#*#<code>#*#*
+        int len = input.length();
+        if (len > 8 && input.startsWith("*#*#") && input.endsWith("#*#*")) {
+            Intent intent = new Intent(TelephonyIntents.SECRET_CODE_ACTION,
+                    Uri.parse("android_secret_code://" + input.substring(4, len - 4)));
+            context.sendBroadcast(intent);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Handle ADN requests by filling in the SIM contact number into the requested
+     * EditText.
+     *
+     * This code works alongside the Asynchronous query handler {@link QueryHandler}
+     * and query cancel handler implemented in {@link SimContactQueryCookie}.
+     */
+    static boolean handleAdnEntry(Context context, String input, EditText textField) {
+        /* ADN entries are of the form "N(N)(N)#" */
+
+        TelephonyManager telephonyManager =
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        if (telephonyManager == null
+                || !TelephonyCapabilities.supportsAdn(telephonyManager.getCurrentPhoneType())) {
+            return false;
+        }
+
+        // if the phone is keyguard-restricted, then just ignore this
+        // input.  We want to make sure that sim card contacts are NOT
+        // exposed unless the phone is unlocked, and this code can be
+        // accessed from the emergency dialer.
+        KeyguardManager keyguardManager =
+                (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
+        if (keyguardManager.inKeyguardRestrictedInputMode()) {
+            return false;
+        }
+
+        int len = input.length();
+        if ((len > 1) && (len < 5) && (input.endsWith("#"))) {
+            try {
+                // get the ordinal number of the sim contact
+                int index = Integer.parseInt(input.substring(0, len-1));
+
+                // The original code that navigated to a SIM Contacts list view did not
+                // highlight the requested contact correctly, a requirement for PTCRB
+                // certification.  This behaviour is consistent with the UI paradigm
+                // for touch-enabled lists, so it does not make sense to try to work
+                // around it.  Instead we fill in the the requested phone number into
+                // the dialer text field.
+
+                // create the async query handler
+                QueryHandler handler = new QueryHandler (context.getContentResolver());
+
+                // create the cookie object
+                SimContactQueryCookie sc = new SimContactQueryCookie(index - 1, handler,
+                        ADN_QUERY_TOKEN);
+
+                // setup the cookie fields
+                sc.contactNum = index - 1;
+                sc.setTextField(textField);
+
+                // create the progress dialog
+                sc.progressDialog = new ProgressDialog(context);
+                sc.progressDialog.setTitle(R.string.simContacts_title);
+                sc.progressDialog.setMessage(context.getText(R.string.simContacts_emptyLoading));
+                sc.progressDialog.setIndeterminate(true);
+                sc.progressDialog.setCancelable(true);
+                sc.progressDialog.setOnCancelListener(sc);
+                sc.progressDialog.getWindow().addFlags(
+                        WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+
+                // display the progress dialog
+                sc.progressDialog.show();
+
+                // run the query.
+                handler.startQuery(ADN_QUERY_TOKEN, sc, Uri.parse("content://icc/adn"),
+                        new String[]{ADN_PHONE_NUMBER_COLUMN_NAME}, null, null, null);
+
+                if (sPreviousAdnQueryHandler != null) {
+                    // It is harmless to call cancel() even after the handler's gone.
+                    sPreviousAdnQueryHandler.cancel();
+                }
+                sPreviousAdnQueryHandler = handler;
+                return true;
+            } catch (NumberFormatException ex) {
+                // Ignore
+            }
+        }
+        return false;
+    }
+
+    static boolean handlePinEntry(Context context, String input) {
+        if ((input.startsWith("**04") || input.startsWith("**05")) && input.endsWith("#")) {
+            try {
+                return ITelephony.Stub.asInterface(ServiceManager.getService("phone"))
+                        .handlePinMmi(input);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to handlePinMmi due to remote exception");
+                return false;
+            }
+        }
+        return false;
+    }
+
+    static boolean handleIMEIDisplay(Context context, String input, boolean useSystemWindow) {
+        TelephonyManager telephonyManager =
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        if (telephonyManager != null && input.equals(MMI_IMEI_DISPLAY)) {
+            int phoneType = telephonyManager.getCurrentPhoneType();
+            if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
+                showIMEIPanel(context, useSystemWindow, telephonyManager);
+                return true;
+            } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
+                showMEIDPanel(context, useSystemWindow, telephonyManager);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    // TODO: Combine showIMEIPanel() and showMEIDPanel() into a single
+    // generic "showDeviceIdPanel()" method, like in the apps/Phone
+    // version of SpecialCharSequenceMgr.java.  (This will require moving
+    // the phone app's TelephonyCapabilities.getDeviceIdLabel() method
+    // into the telephony framework, though.)
+
+    private static void showIMEIPanel(Context context, boolean useSystemWindow,
+            TelephonyManager telephonyManager) {
+        String imeiStr = telephonyManager.getDeviceId();
+
+        AlertDialog alert = new AlertDialog.Builder(context)
+                .setTitle(R.string.imei)
+                .setMessage(imeiStr)
+                .setPositiveButton(android.R.string.ok, null)
+                .setCancelable(false)
+                .show();
+    }
+
+    private static void showMEIDPanel(Context context, boolean useSystemWindow,
+            TelephonyManager telephonyManager) {
+        String meidStr = telephonyManager.getDeviceId();
+
+        AlertDialog alert = new AlertDialog.Builder(context)
+                .setTitle(R.string.meid)
+                .setMessage(meidStr)
+                .setPositiveButton(android.R.string.ok, null)
+                .setCancelable(false)
+                .show();
+    }
+
+    /*******
+     * This code is used to handle SIM Contact queries
+     *******/
+    private static final String ADN_PHONE_NUMBER_COLUMN_NAME = "number";
+    private static final String ADN_NAME_COLUMN_NAME = "name";
+    private static final int ADN_QUERY_TOKEN = -1;
+
+    /**
+     * Cookie object that contains everything we need to communicate to the
+     * handler's onQuery Complete, as well as what we need in order to cancel
+     * the query (if requested).
+     *
+     * Note, access to the textField field is going to be synchronized, because
+     * the user can request a cancel at any time through the UI.
+     */
+    private static class SimContactQueryCookie implements DialogInterface.OnCancelListener{
+        public ProgressDialog progressDialog;
+        public int contactNum;
+
+        // Used to identify the query request.
+        private int mToken;
+        private QueryHandler mHandler;
+
+        // The text field we're going to update
+        private EditText textField;
+
+        public SimContactQueryCookie(int number, QueryHandler handler, int token) {
+            contactNum = number;
+            mHandler = handler;
+            mToken = token;
+        }
+
+        /**
+         * Synchronized getter for the EditText.
+         */
+        public synchronized EditText getTextField() {
+            return textField;
+        }
+
+        /**
+         * Synchronized setter for the EditText.
+         */
+        public synchronized void setTextField(EditText text) {
+            textField = text;
+        }
+
+        /**
+         * Cancel the ADN query by stopping the operation and signaling
+         * the cookie that a cancel request is made.
+         */
+        public synchronized void onCancel(DialogInterface dialog) {
+            // close the progress dialog
+            if (progressDialog != null) {
+                progressDialog.dismiss();
+            }
+
+            // setting the textfield to null ensures that the UI does NOT get
+            // updated.
+            textField = null;
+
+            // Cancel the operation if possible.
+            mHandler.cancelOperation(mToken);
+        }
+    }
+
+    /**
+     * Asynchronous query handler that services requests to look up ADNs
+     *
+     * Queries originate from {@link handleAdnEntry}.
+     */
+    private static class QueryHandler extends AsyncQueryHandler {
+
+        private boolean mCanceled;
+
+        public QueryHandler(ContentResolver cr) {
+            super(cr);
+        }
+
+        /**
+         * Override basic onQueryComplete to fill in the textfield when
+         * we're handed the ADN cursor.
+         */
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor c) {
+            sPreviousAdnQueryHandler = null;
+            if (mCanceled) {
+                return;
+            }
+
+            SimContactQueryCookie sc = (SimContactQueryCookie) cookie;
+
+            // close the progress dialog.
+            sc.progressDialog.dismiss();
+
+            // get the EditText to update or see if the request was cancelled.
+            EditText text = sc.getTextField();
+
+            // if the textview is valid, and the cursor is valid and postionable
+            // on the Nth number, then we update the text field and display a
+            // toast indicating the caller name.
+            if ((c != null) && (text != null) && (c.moveToPosition(sc.contactNum))) {
+                String name = c.getString(c.getColumnIndexOrThrow(ADN_NAME_COLUMN_NAME));
+                String number = c.getString(c.getColumnIndexOrThrow(ADN_PHONE_NUMBER_COLUMN_NAME));
+
+                // fill the text in.
+                text.getText().replace(0, 0, number);
+
+                // display the name as a toast
+                Context context = sc.progressDialog.getContext();
+                name = context.getString(R.string.menu_callNumber, name);
+                Toast.makeText(context, name, Toast.LENGTH_SHORT)
+                    .show();
+            }
+        }
+
+        public void cancel() {
+            mCanceled = true;
+            // Ask AsyncQueryHandler to cancel the whole request. This will fails when the
+            // query already started.
+            cancelOperation(ADN_QUERY_TOKEN);
+        }
+    }
+}
diff --git a/src/com/android/dialer/calllog/CallDetailHistoryAdapter.java b/src/com/android/dialer/calllog/CallDetailHistoryAdapter.java
index 38dc727..0763f3c 100644
--- a/src/com/android/dialer/calllog/CallDetailHistoryAdapter.java
+++ b/src/com/android/dialer/calllog/CallDetailHistoryAdapter.java
@@ -25,8 +25,8 @@
 import android.widget.BaseAdapter;
 import android.widget.TextView;
 
-import com.android.dialer.PhoneCallDetails;
 import com.android.contacts.R;
+import com.android.dialer.PhoneCallDetails;
 
 /**
  * Adapter for a ListView containing history items from the details of a call.
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index 217f597..720cc11 100644
--- a/src/com/android/dialer/calllog/CallLogAdapter.java
+++ b/src/com/android/dialer/calllog/CallLogAdapter.java
@@ -33,11 +33,11 @@
 
 import com.android.common.widget.GroupingListAdapter;
 import com.android.contacts.ContactPhotoManager;
+import com.android.contacts.R;
+import com.android.contacts.util.UriUtils;
 import com.android.dialer.PhoneCallDetails;
 import com.android.dialer.PhoneCallDetailsHelper;
-import com.android.contacts.R;
 import com.android.dialer.util.ExpirableCache;
-import com.android.contacts.util.UriUtils;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Objects;
 
diff --git a/src/com/android/dialer/calllog/CallLogFragment.java b/src/com/android/dialer/calllog/CallLogFragment.java
index 4b31134..83ed830 100644
--- a/src/com/android/dialer/calllog/CallLogFragment.java
+++ b/src/com/android/dialer/calllog/CallLogFragment.java
@@ -49,7 +49,7 @@
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
 import com.android.contacts.util.Constants;
-import com.android.contacts.util.EmptyLoader;
+import com.android.dialer.util.EmptyLoader;
 import com.android.dialer.voicemail.VoicemailStatusHelper;
 import com.android.dialer.voicemail.VoicemailStatusHelper.StatusMessage;
 import com.android.dialer.voicemail.VoicemailStatusHelperImpl;
diff --git a/src/com/android/dialer/calllog/CallLogListItemHelper.java b/src/com/android/dialer/calllog/CallLogListItemHelper.java
index 7862a56..101ca7d 100644
--- a/src/com/android/dialer/calllog/CallLogListItemHelper.java
+++ b/src/com/android/dialer/calllog/CallLogListItemHelper.java
@@ -21,9 +21,9 @@
 import android.text.TextUtils;
 import android.view.View;
 
+import com.android.contacts.R;
 import com.android.dialer.PhoneCallDetails;
 import com.android.dialer.PhoneCallDetailsHelper;
-import com.android.contacts.R;
 
 /**
  * Helper class to fill in the views of a call log entry.
diff --git a/src/com/android/dialer/calllog/CallLogListItemViews.java b/src/com/android/dialer/calllog/CallLogListItemViews.java
index 5b860ef..ac6ad95 100644
--- a/src/com/android/dialer/calllog/CallLogListItemViews.java
+++ b/src/com/android/dialer/calllog/CallLogListItemViews.java
@@ -22,9 +22,9 @@
 import android.widget.QuickContactBadge;
 import android.widget.TextView;
 
-import com.android.dialer.PhoneCallDetailsViews;
 import com.android.contacts.R;
 import com.android.contacts.test.NeededForTesting;
+import com.android.dialer.PhoneCallDetailsViews;
 
 /**
  * Simple value object containing the various views within a call log entry.
diff --git a/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java b/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java
index 0f6fe3b..ff4e5ee 100644
--- a/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java
+++ b/src/com/android/dialer/calllog/DefaultVoicemailNotifier.java
@@ -32,8 +32,8 @@
 import android.util.Log;
 
 import com.android.common.io.MoreCloseables;
-import com.android.dialer.CallDetailActivity;
 import com.android.contacts.R;
+import com.android.dialer.CallDetailActivity;
 import com.google.common.collect.Maps;
 
 import java.util.Map;
diff --git a/src/com/android/dialer/calllog/IntentProvider.java b/src/com/android/dialer/calllog/IntentProvider.java
index f43dc51..859487a 100644
--- a/src/com/android/dialer/calllog/IntentProvider.java
+++ b/src/com/android/dialer/calllog/IntentProvider.java
@@ -23,8 +23,8 @@
 import android.net.Uri;
 import android.provider.CallLog.Calls;
 
-import com.android.dialer.CallDetailActivity;
 import com.android.contacts.ContactsUtils;
+import com.android.dialer.CallDetailActivity;
 
 /**
  * Used to create an intent to attach to an action in the call log.
diff --git a/src/com/android/dialer/dialpad/DialpadFragment.java b/src/com/android/dialer/dialpad/DialpadFragment.java
index 65cab56..77b7c07 100644
--- a/src/com/android/dialer/dialpad/DialpadFragment.java
+++ b/src/com/android/dialer/dialpad/DialpadFragment.java
@@ -69,11 +69,11 @@
 
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
-import com.android.contacts.SpecialCharSequenceMgr;
-import com.android.dialer.DialtactsActivity;
 import com.android.contacts.util.Constants;
 import com.android.contacts.util.PhoneNumberFormatter;
 import com.android.contacts.util.StopWatch;
+import com.android.dialer.DialtactsActivity;
+import com.android.dialer.SpecialCharSequenceMgr;
 import com.android.internal.telephony.ITelephony;
 import com.android.phone.common.CallLogAsync;
 import com.android.phone.common.HapticFeedback;
diff --git a/src/com/android/dialer/list/PhoneFavoriteFragment.java b/src/com/android/dialer/list/PhoneFavoriteFragment.java
new file mode 100644
index 0000000..157e82f
--- /dev/null
+++ b/src/com/android/dialer/list/PhoneFavoriteFragment.java
@@ -0,0 +1,569 @@
+/*
+ * 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.dialer.list;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.LoaderManager;
+import android.content.CursorLoader;
+import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Directory;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.FrameLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.android.contacts.ContactPhotoManager;
+import com.android.contacts.ContactTileLoaderFactory;
+import com.android.contacts.R;
+import com.android.contacts.dialog.ClearFrequentsDialog;
+import com.android.contacts.interactions.ImportExportDialogFragment;
+import com.android.contacts.list.ContactListFilter;
+import com.android.contacts.list.ContactListFilterController;
+import com.android.contacts.list.ContactListItemView;
+import com.android.contacts.list.ContactTileAdapter;
+import com.android.contacts.list.ContactTileView;
+import com.android.contacts.list.PhoneNumberListAdapter;
+import com.android.contacts.preference.ContactsPreferences;
+import com.android.contacts.util.AccountFilterUtil;
+
+/**
+ * Fragment for Phone UI's favorite screen.
+ *
+ * This fragment contains three kinds of contacts in one screen: "starred", "frequent", and "all"
+ * contacts. To show them at once, this merges results from {@link com.android.contacts.list.ContactTileAdapter} and
+ * {@link com.android.contacts.list.PhoneNumberListAdapter} into one unified list using {@link PhoneFavoriteMergedAdapter}.
+ * A contact filter header is also inserted between those adapters' results.
+ */
+public class PhoneFavoriteFragment extends Fragment implements OnItemClickListener {
+    private static final String TAG = PhoneFavoriteFragment.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    /**
+     * Used with LoaderManager.
+     */
+    private static int LOADER_ID_CONTACT_TILE = 1;
+    private static int LOADER_ID_ALL_CONTACTS = 2;
+
+    private static final String KEY_FILTER = "filter";
+
+    private static final int REQUEST_CODE_ACCOUNT_FILTER = 1;
+
+    public interface Listener {
+        public void onContactSelected(Uri contactUri);
+        public void onCallNumberDirectly(String phoneNumber);
+    }
+
+    private class ContactTileLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> {
+        @Override
+        public CursorLoader onCreateLoader(int id, Bundle args) {
+            if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onCreateLoader.");
+            return ContactTileLoaderFactory.createStrequentPhoneOnlyLoader(getActivity());
+        }
+
+        @Override
+        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+            if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onLoadFinished");
+            mContactTileAdapter.setContactCursor(data);
+
+            if (mAllContactsForceReload) {
+                mAllContactsAdapter.onDataReload();
+                // Use restartLoader() to make LoaderManager to load the section again.
+                getLoaderManager().restartLoader(
+                        LOADER_ID_ALL_CONTACTS, null, mAllContactsLoaderListener);
+            } else if (!mAllContactsLoaderStarted) {
+                // Load "all" contacts if not loaded yet.
+                getLoaderManager().initLoader(
+                        LOADER_ID_ALL_CONTACTS, null, mAllContactsLoaderListener);
+            }
+            mAllContactsForceReload = false;
+            mAllContactsLoaderStarted = true;
+
+            // Show the filter header with "loading" state.
+            updateFilterHeaderView();
+            mAccountFilterHeader.setVisibility(View.VISIBLE);
+
+            // invalidate the options menu if needed
+            invalidateOptionsMenuIfNeeded();
+        }
+
+        @Override
+        public void onLoaderReset(Loader<Cursor> loader) {
+            if (DEBUG) Log.d(TAG, "ContactTileLoaderListener#onLoaderReset. ");
+        }
+    }
+
+    private class AllContactsLoaderListener implements LoaderManager.LoaderCallbacks<Cursor> {
+        @Override
+        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+            if (DEBUG) Log.d(TAG, "AllContactsLoaderListener#onCreateLoader");
+            CursorLoader loader = new CursorLoader(getActivity(), null, null, null, null, null);
+            mAllContactsAdapter.configureLoader(loader, Directory.DEFAULT);
+            return loader;
+        }
+
+        @Override
+        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+            if (DEBUG) Log.d(TAG, "AllContactsLoaderListener#onLoadFinished");
+            mAllContactsAdapter.changeCursor(0, data);
+            updateFilterHeaderView();
+            mHandler.removeMessages(MESSAGE_SHOW_LOADING_EFFECT);
+            mLoadingView.setVisibility(View.VISIBLE);
+        }
+
+        @Override
+        public void onLoaderReset(Loader<Cursor> loader) {
+            if (DEBUG) Log.d(TAG, "AllContactsLoaderListener#onLoaderReset. ");
+        }
+    }
+
+    private class ContactTileAdapterListener implements ContactTileView.Listener {
+        @Override
+        public void onContactSelected(Uri contactUri, Rect targetRect) {
+            if (mListener != null) {
+                mListener.onContactSelected(contactUri);
+            }
+        }
+
+        @Override
+        public void onCallNumberDirectly(String phoneNumber) {
+            if (mListener != null) {
+                mListener.onCallNumberDirectly(phoneNumber);
+            }
+        }
+
+        @Override
+        public int getApproximateTileWidth() {
+            return getView().getWidth() / mContactTileAdapter.getColumnCount();
+        }
+    }
+
+    private class FilterHeaderClickListener implements OnClickListener {
+        @Override
+        public void onClick(View view) {
+            AccountFilterUtil.startAccountFilterActivityForResult(
+                    PhoneFavoriteFragment.this,
+                    REQUEST_CODE_ACCOUNT_FILTER,
+                    mFilter);
+        }
+    }
+
+    private class ContactsPreferenceChangeListener
+            implements ContactsPreferences.ChangeListener {
+        @Override
+        public void onChange() {
+            if (loadContactsPreferences()) {
+                requestReloadAllContacts();
+            }
+        }
+    }
+
+    private class ScrollListener implements ListView.OnScrollListener {
+        private boolean mShouldShowFastScroller;
+        @Override
+        public void onScroll(AbsListView view,
+                int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+            // FastScroller should be visible only when the user is seeing "all" contacts section.
+            final boolean shouldShow = mAdapter.shouldShowFirstScroller(firstVisibleItem);
+            if (shouldShow != mShouldShowFastScroller) {
+                mListView.setVerticalScrollBarEnabled(shouldShow);
+                mListView.setFastScrollEnabled(shouldShow);
+                mListView.setFastScrollAlwaysVisible(shouldShow);
+                mShouldShowFastScroller = shouldShow;
+            }
+        }
+
+        @Override
+        public void onScrollStateChanged(AbsListView view, int scrollState) {
+        }
+    }
+
+    private static final int MESSAGE_SHOW_LOADING_EFFECT = 1;
+    private static final int LOADING_EFFECT_DELAY = 500;  // ms
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_SHOW_LOADING_EFFECT:
+                    mLoadingView.setVisibility(View.VISIBLE);
+                    break;
+            }
+        }
+    };
+
+    private Listener mListener;
+    private PhoneFavoriteMergedAdapter mAdapter;
+    private ContactTileAdapter mContactTileAdapter;
+    private PhoneNumberListAdapter mAllContactsAdapter;
+
+    /**
+     * true when the loader for {@link PhoneNumberListAdapter} has started already.
+     */
+    private boolean mAllContactsLoaderStarted;
+    /**
+     * true when the loader for {@link PhoneNumberListAdapter} must reload "all" contacts again.
+     * It typically happens when {@link ContactsPreferences} has changed its settings
+     * (display order and sort order)
+     */
+    private boolean mAllContactsForceReload;
+
+    private ContactsPreferences mContactsPrefs;
+    private ContactListFilter mFilter;
+
+    private TextView mEmptyView;
+    private ListView mListView;
+    /**
+     * Layout containing {@link #mAccountFilterHeader}. Used to limit area being "pressed".
+     */
+    private FrameLayout mAccountFilterHeaderContainer;
+    private View mAccountFilterHeader;
+
+    /**
+     * Layout used when contacts load is slower than expected and thus "loading" view should be
+     * shown.
+     */
+    private View mLoadingView;
+
+    private final ContactTileView.Listener mContactTileAdapterListener =
+            new ContactTileAdapterListener();
+    private final LoaderManager.LoaderCallbacks<Cursor> mContactTileLoaderListener =
+            new ContactTileLoaderListener();
+    private final LoaderManager.LoaderCallbacks<Cursor> mAllContactsLoaderListener =
+            new AllContactsLoaderListener();
+    private final OnClickListener mFilterHeaderClickListener = new FilterHeaderClickListener();
+    private final ContactsPreferenceChangeListener mContactsPreferenceChangeListener =
+            new ContactsPreferenceChangeListener();
+    private final ScrollListener mScrollListener = new ScrollListener();
+
+    private boolean mOptionsMenuHasFrequents;
+
+    @Override
+    public void onAttach(Activity activity) {
+        if (DEBUG) Log.d(TAG, "onAttach()");
+        super.onAttach(activity);
+
+        mContactsPrefs = new ContactsPreferences(activity);
+
+        // Construct two base adapters which will become part of PhoneFavoriteMergedAdapter.
+        // We don't construct the resultant adapter at this moment since it requires LayoutInflater
+        // that will be available on onCreateView().
+
+        mContactTileAdapter = new ContactTileAdapter(activity, mContactTileAdapterListener,
+                getResources().getInteger(R.integer.contact_tile_column_count_in_favorites),
+                ContactTileAdapter.DisplayType.STREQUENT_PHONE_ONLY);
+        mContactTileAdapter.setPhotoLoader(ContactPhotoManager.getInstance(activity));
+
+        // Setup the "all" adapter manually. See also the setup logic in ContactEntryListFragment.
+        mAllContactsAdapter = new PhoneNumberListAdapter(activity);
+        mAllContactsAdapter.setDisplayPhotos(true);
+        mAllContactsAdapter.setQuickContactEnabled(true);
+        mAllContactsAdapter.setSearchMode(false);
+        mAllContactsAdapter.setIncludeProfile(false);
+        mAllContactsAdapter.setSelectionVisible(false);
+        mAllContactsAdapter.setDarkTheme(true);
+        mAllContactsAdapter.setPhotoLoader(ContactPhotoManager.getInstance(activity));
+        // Disable directory header.
+        mAllContactsAdapter.setHasHeader(0, false);
+        // Show A-Z section index.
+        mAllContactsAdapter.setSectionHeaderDisplayEnabled(true);
+        // Disable pinned header. It doesn't work with this fragment.
+        mAllContactsAdapter.setPinnedPartitionHeadersEnabled(false);
+        // Put photos on left for consistency with "frequent" contacts section.
+        mAllContactsAdapter.setPhotoPosition(ContactListItemView.PhotoPosition.LEFT);
+
+        // Use Callable.CONTENT_URI which will include not only phone numbers but also SIP
+        // addresses.
+        mAllContactsAdapter.setUseCallableUri(true);
+
+        mAllContactsAdapter.setContactNameDisplayOrder(mContactsPrefs.getDisplayOrder());
+        mAllContactsAdapter.setSortOrder(mContactsPrefs.getSortOrder());
+    }
+
+    @Override
+    public void onCreate(Bundle savedState) {
+        if (DEBUG) Log.d(TAG, "onCreate()");
+        super.onCreate(savedState);
+        if (savedState != null) {
+            mFilter = savedState.getParcelable(KEY_FILTER);
+
+            if (mFilter != null) {
+                mAllContactsAdapter.setFilter(mFilter);
+            }
+        }
+        setHasOptionsMenu(true);
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putParcelable(KEY_FILTER, mFilter);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        final View listLayout = inflater.inflate(
+                R.layout.phone_contact_tile_list, container, false);
+
+        mListView = (ListView) listLayout.findViewById(R.id.contact_tile_list);
+        mListView.setItemsCanFocus(true);
+        mListView.setOnItemClickListener(this);
+        mListView.setVerticalScrollBarEnabled(false);
+        mListView.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_RIGHT);
+        mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
+
+        // Create the account filter header but keep it hidden until "all" contacts are loaded.
+        mAccountFilterHeaderContainer = new FrameLayout(getActivity(), null);
+        mAccountFilterHeader = inflater.inflate(R.layout.account_filter_header_for_phone_favorite,
+                mListView, false);
+        mAccountFilterHeader.setOnClickListener(mFilterHeaderClickListener);
+        mAccountFilterHeaderContainer.addView(mAccountFilterHeader);
+
+        mLoadingView = inflater.inflate(R.layout.phone_loading_contacts, mListView, false);
+
+        mAdapter = new PhoneFavoriteMergedAdapter(getActivity(),
+                mContactTileAdapter, mAccountFilterHeaderContainer, mAllContactsAdapter,
+                mLoadingView);
+
+        mListView.setAdapter(mAdapter);
+
+        mListView.setOnScrollListener(mScrollListener);
+        mListView.setFastScrollEnabled(false);
+        mListView.setFastScrollAlwaysVisible(false);
+
+        mEmptyView = (TextView) listLayout.findViewById(R.id.contact_tile_list_empty);
+        mEmptyView.setText(getString(R.string.listTotalAllContactsZero));
+        mListView.setEmptyView(mEmptyView);
+
+        updateFilterHeaderView();
+
+        return listLayout;
+    }
+
+    private boolean isOptionsMenuChanged() {
+        return mOptionsMenuHasFrequents != hasFrequents();
+    }
+
+    private void invalidateOptionsMenuIfNeeded() {
+        if (isOptionsMenuChanged()) {
+            getActivity().invalidateOptionsMenu();
+        }
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+        inflater.inflate(R.menu.phone_favorite_options, menu);
+    }
+
+    @Override
+    public void onPrepareOptionsMenu(Menu menu) {
+        final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents);
+        mOptionsMenuHasFrequents = hasFrequents();
+        clearFrequents.setVisible(mOptionsMenuHasFrequents);
+    }
+
+    private boolean hasFrequents() {
+        return mContactTileAdapter.getNumFrequents() > 0;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.menu_import_export:
+                // We hard-code the "contactsAreAvailable" argument because doing it properly would
+                // involve querying a {@link ProviderStatusLoader}, which we don't want to do right
+                // now in Dialtacts for (potential) performance reasons.  Compare with how it is
+                // done in {@link PeopleActivity}.
+                ImportExportDialogFragment.show(getFragmentManager(), true);
+                return true;
+            case R.id.menu_accounts:
+                final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
+                intent.putExtra(Settings.EXTRA_AUTHORITIES, new String[] {
+                    ContactsContract.AUTHORITY
+                });
+                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+                startActivity(intent);
+                return true;
+            case R.id.menu_clear_frequents:
+                ClearFrequentsDialog.show(getFragmentManager());
+                return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        mContactsPrefs.registerChangeListener(mContactsPreferenceChangeListener);
+
+        // If ContactsPreferences has changed, we need to reload "all" contacts with the new
+        // settings. If mAllContactsFoarceReload is already true, it should be kept.
+        if (loadContactsPreferences()) {
+            mAllContactsForceReload = true;
+        }
+
+        // Use initLoader() instead of restartLoader() to refraining unnecessary reload.
+        // This method call implicitly assures ContactTileLoaderListener's onLoadFinished() will
+        // be called, on which we'll check if "all" contacts should be reloaded again or not.
+        getLoaderManager().initLoader(LOADER_ID_CONTACT_TILE, null, mContactTileLoaderListener);
+
+        // Delay showing "loading" view until certain amount of time so that users won't see
+        // instant flash of the view when the contacts load is fast enough.
+        // This will be kept shown until both tile and all sections are loaded.
+        mLoadingView.setVisibility(View.INVISIBLE);
+        mHandler.sendEmptyMessageDelayed(MESSAGE_SHOW_LOADING_EFFECT, LOADING_EFFECT_DELAY);
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mContactsPrefs.unregisterChangeListener();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * This is only effective for elements provided by {@link #mContactTileAdapter}.
+     * {@link #mContactTileAdapter} has its own logic for click events.
+     */
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        final int contactTileAdapterCount = mContactTileAdapter.getCount();
+        if (position <= contactTileAdapterCount) {
+            Log.e(TAG, "onItemClick() event for unexpected position. "
+                    + "The position " + position + " is before \"all\" section. Ignored.");
+        } else {
+            final int localPosition = position - mContactTileAdapter.getCount() - 1;
+            if (mListener != null) {
+                mListener.onContactSelected(mAllContactsAdapter.getDataUri(localPosition));
+            }
+        }
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == REQUEST_CODE_ACCOUNT_FILTER) {
+            if (getActivity() != null) {
+                AccountFilterUtil.handleAccountFilterResult(
+                        ContactListFilterController.getInstance(getActivity()), resultCode, data);
+            } else {
+                Log.e(TAG, "getActivity() returns null during Fragment#onActivityResult()");
+            }
+        }
+    }
+
+    private boolean loadContactsPreferences() {
+        if (mContactsPrefs == null || mAllContactsAdapter == null) {
+            return false;
+        }
+
+        boolean changed = false;
+        final int currentDisplayOrder = mContactsPrefs.getDisplayOrder();
+        if (mAllContactsAdapter.getContactNameDisplayOrder() != currentDisplayOrder) {
+            mAllContactsAdapter.setContactNameDisplayOrder(currentDisplayOrder);
+            changed = true;
+        }
+
+        final int currentSortOrder = mContactsPrefs.getSortOrder();
+        if (mAllContactsAdapter.getSortOrder() != currentSortOrder) {
+            mAllContactsAdapter.setSortOrder(currentSortOrder);
+            changed = true;
+        }
+
+        return changed;
+    }
+
+    /**
+     * Requests to reload "all" contacts. If the section is already loaded, this method will
+     * force reloading it now. If the section isn't loaded yet, the actual load may be done later
+     * (on {@link #onStart()}.
+     */
+    private void requestReloadAllContacts() {
+        if (DEBUG) {
+            Log.d(TAG, "requestReloadAllContacts()"
+                    + " mAllContactsAdapter: " + mAllContactsAdapter
+                    + ", mAllContactsLoaderStarted: " + mAllContactsLoaderStarted);
+        }
+
+        if (mAllContactsAdapter == null || !mAllContactsLoaderStarted) {
+            // Remember this request until next load on onStart().
+            mAllContactsForceReload = true;
+            return;
+        }
+
+        if (DEBUG) Log.d(TAG, "Reload \"all\" contacts now.");
+
+        mAllContactsAdapter.onDataReload();
+        // Use restartLoader() to make LoaderManager to load the section again.
+        getLoaderManager().restartLoader(LOADER_ID_ALL_CONTACTS, null, mAllContactsLoaderListener);
+    }
+
+    private void updateFilterHeaderView() {
+        final ContactListFilter filter = getFilter();
+        if (mAccountFilterHeader == null || mAllContactsAdapter == null || filter == null) {
+            return;
+        }
+        AccountFilterUtil.updateAccountFilterTitleForPhone(mAccountFilterHeader, filter, true);
+    }
+
+    public ContactListFilter getFilter() {
+        return mFilter;
+    }
+
+    public void setFilter(ContactListFilter filter) {
+        if ((mFilter == null && filter == null) || (mFilter != null && mFilter.equals(filter))) {
+            return;
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "setFilter(). old filter (" + mFilter
+                    + ") will be replaced with new filter (" + filter + ")");
+        }
+
+        mFilter = filter;
+
+        if (mAllContactsAdapter != null) {
+            mAllContactsAdapter.setFilter(mFilter);
+            requestReloadAllContacts();
+            updateFilterHeaderView();
+        }
+    }
+
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+}
diff --git a/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java b/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java
new file mode 100644
index 0000000..8e23399
--- /dev/null
+++ b/src/com/android/dialer/list/PhoneFavoriteMergedAdapter.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ * Licensed to 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.dialer.list;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.FrameLayout;
+import android.widget.SectionIndexer;
+
+import com.android.contacts.R;
+import com.android.contacts.list.ContactEntryListAdapter;
+import com.android.contacts.list.ContactListItemView;
+import com.android.contacts.list.ContactTileAdapter;
+
+/**
+ * An adapter that combines items from {@link com.android.contacts.list.ContactTileAdapter} and
+ * {@link com.android.contacts.list.ContactEntryListAdapter} into a single list. In between those two results,
+ * an account filter header will be inserted.
+ */
+public class PhoneFavoriteMergedAdapter extends BaseAdapter implements SectionIndexer {
+
+    private class CustomDataSetObserver extends DataSetObserver {
+        @Override
+        public void onChanged() {
+            notifyDataSetChanged();
+        }
+    }
+
+    private final ContactTileAdapter mContactTileAdapter;
+    private final ContactEntryListAdapter mContactEntryListAdapter;
+    private final View mAccountFilterHeaderContainer;
+    private final View mLoadingView;
+
+    private final int mItemPaddingLeft;
+    private final int mItemPaddingRight;
+
+    // Make frequent header consistent with account filter header.
+    private final int mFrequentHeaderPaddingTop;
+
+    private final DataSetObserver mObserver;
+
+    public PhoneFavoriteMergedAdapter(Context context,
+            ContactTileAdapter contactTileAdapter,
+            View accountFilterHeaderContainer,
+            ContactEntryListAdapter contactEntryListAdapter,
+            View loadingView) {
+        Resources resources = context.getResources();
+        mItemPaddingLeft = resources.getDimensionPixelSize(R.dimen.detail_item_side_margin);
+        mItemPaddingRight = resources.getDimensionPixelSize(R.dimen.list_visible_scrollbar_padding);
+        mFrequentHeaderPaddingTop = resources.getDimensionPixelSize(
+                R.dimen.contact_browser_list_top_margin);
+        mContactTileAdapter = contactTileAdapter;
+        mContactEntryListAdapter = contactEntryListAdapter;
+
+        mAccountFilterHeaderContainer = accountFilterHeaderContainer;
+
+        mObserver = new CustomDataSetObserver();
+        mContactTileAdapter.registerDataSetObserver(mObserver);
+        mContactEntryListAdapter.registerDataSetObserver(mObserver);
+
+        mLoadingView = loadingView;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        // Cannot use the super's method here because we add extra rows in getCount() to account
+        // for headers
+        return mContactTileAdapter.getCount() + mContactEntryListAdapter.getCount() == 0;
+    }
+
+    @Override
+    public int getCount() {
+        final int contactTileAdapterCount = mContactTileAdapter.getCount();
+        final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
+        if (mContactEntryListAdapter.isLoading()) {
+            // Hide "all" contacts during its being loaded. Instead show "loading" view.
+            //
+            // "+2" for mAccountFilterHeaderContainer and mLoadingView
+            return contactTileAdapterCount + 2;
+        } else {
+            // "+1" for mAccountFilterHeaderContainer
+            return contactTileAdapterCount + contactEntryListAdapterCount + 1;
+        }
+    }
+
+    @Override
+    public Object getItem(int position) {
+        final int contactTileAdapterCount = mContactTileAdapter.getCount();
+        final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
+        if (position < contactTileAdapterCount) {  // For "tile" and "frequent" sections
+            return mContactTileAdapter.getItem(position);
+        } else if (position == contactTileAdapterCount) {  // For "all" section's account header
+            return mAccountFilterHeaderContainer;
+        } else {  // For "all" section
+            if (mContactEntryListAdapter.isLoading()) {  // "All" section is being loaded.
+                return mLoadingView;
+            } else {
+                // "-1" for mAccountFilterHeaderContainer
+                final int localPosition = position - contactTileAdapterCount - 1;
+                return mContactTileAdapter.getItem(localPosition);
+            }
+        }
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        // "+2" for mAccountFilterHeaderContainer and mLoadingView
+        return (mContactTileAdapter.getViewTypeCount()
+                + mContactEntryListAdapter.getViewTypeCount()
+                + 2);
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        final int contactTileAdapterCount = mContactTileAdapter.getCount();
+        final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
+        // There should be four kinds of types that are usually used, and one more exceptional
+        // type (IGNORE_ITEM_VIEW_TYPE), which sometimes comes from mContactTileAdapter.
+        //
+        // The four ordinary view types have the index equal to or more than 0, and less than
+        // mContactTileAdapter.getViewTypeCount()+ mContactEntryListAdapter.getViewTypeCount() + 2.
+        // (See also this class's getViewTypeCount())
+        //
+        // We have those values for:
+        // - The view types mContactTileAdapter originally has
+        // - The view types mContactEntryListAdapter originally has
+        // - mAccountFilterHeaderContainer ("all" section's account header), and
+        // - mLoadingView
+        //
+        // Those types should not be mixed, so we have a different range for each kinds of types:
+        // - Types for mContactTileAdapter ("tile" and "frequent" sections)
+        //   They should have the index, >=0 and <mContactTileAdapter.getViewTypeCount()
+        //
+        // - Types for mContactEntryListAdapter ("all" sections)
+        //   They should have the index, >=mContactTileAdapter.getViewTypeCount() and
+        //   <(mContactTileAdapter.getViewTypeCount() + mContactEntryListAdapter.getViewTypeCount())
+        //
+        // - Type for "all" section's account header
+        //   It should have the exact index
+        //   mContactTileAdapter.getViewTypeCount()+ mContactEntryListAdapter.getViewTypeCount()
+        //
+        // - Type for "loading" view used during "all" section is being loaded.
+        //   It should have the exact index
+        //   mContactTileAdapter.getViewTypeCount()+ mContactEntryListAdapter.getViewTypeCount() + 1
+        //
+        // As an exception, IGNORE_ITEM_VIEW_TYPE (-1) will be remained as is, which will be used
+        // by framework's Adapter implementation and thus should be left as is.
+        if (position < contactTileAdapterCount) {  // For "tile" and "frequent" sections
+            return mContactTileAdapter.getItemViewType(position);
+        } else if (position == contactTileAdapterCount) {  // For "all" section's account header
+            return mContactTileAdapter.getViewTypeCount()
+                    + mContactEntryListAdapter.getViewTypeCount();
+        } else {  // For "all" section
+            if (mContactEntryListAdapter.isLoading()) {  // "All" section is being loaded.
+                return mContactTileAdapter.getViewTypeCount()
+                        + mContactEntryListAdapter.getViewTypeCount() + 1;
+            } else {
+                // "-1" for mAccountFilterHeaderContainer
+                final int localPosition = position - contactTileAdapterCount - 1;
+                final int type = mContactEntryListAdapter.getItemViewType(localPosition);
+                // IGNORE_ITEM_VIEW_TYPE must be handled differently.
+                return (type < 0) ? type : type + mContactTileAdapter.getViewTypeCount();
+            }
+        }
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        final int contactTileAdapterCount = mContactTileAdapter.getCount();
+        final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
+
+        // Obtain a View relevant for that position, and adjust its horizontal padding. Each
+        // View has different implementation, so we use different way to control those padding.
+        if (position < contactTileAdapterCount) {  // For "tile" and "frequent" sections
+            final View view = mContactTileAdapter.getView(position, convertView, parent);
+            final int frequentHeaderPosition = mContactTileAdapter.getFrequentHeaderPosition();
+            if (position < frequentHeaderPosition) {  // "starred" contacts
+                // No padding adjustment.
+            } else if (position == frequentHeaderPosition) {
+                view.setPadding(mItemPaddingLeft, mFrequentHeaderPaddingTop,
+                        mItemPaddingRight, view.getPaddingBottom());
+            } else {
+                // Views for "frequent" contacts use FrameLayout's margins instead of padding.
+                final FrameLayout frameLayout = (FrameLayout) view;
+                final View child = frameLayout.getChildAt(0);
+                FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+                        FrameLayout.LayoutParams.WRAP_CONTENT,
+                        FrameLayout.LayoutParams.WRAP_CONTENT);
+                params.setMargins(mItemPaddingLeft, 0, mItemPaddingRight, 0);
+                child.setLayoutParams(params);
+            }
+            return view;
+        } else if (position == contactTileAdapterCount) {  // For "all" section's account header
+            mAccountFilterHeaderContainer.setPadding(mItemPaddingLeft,
+                    mAccountFilterHeaderContainer.getPaddingTop(),
+                    mItemPaddingRight,
+                    mAccountFilterHeaderContainer.getPaddingBottom());
+            return mAccountFilterHeaderContainer;
+        } else {  // For "all" section
+            if (mContactEntryListAdapter.isLoading()) {  // "All" section is being loaded.
+                mLoadingView.setPadding(mItemPaddingLeft,
+                        mLoadingView.getPaddingTop(),
+                        mItemPaddingRight,
+                        mLoadingView.getPaddingBottom());
+                return mLoadingView;
+            } else {
+                // "-1" for mAccountFilterHeaderContainer
+                final int localPosition = position - contactTileAdapterCount - 1;
+                final ContactListItemView itemView = (ContactListItemView)
+                        mContactEntryListAdapter.getView(localPosition, convertView, null);
+                itemView.setPadding(mItemPaddingLeft, itemView.getPaddingTop(),
+                        mItemPaddingRight, itemView.getPaddingBottom());
+                itemView.setSelectionBoundsHorizontalMargin(mItemPaddingLeft, mItemPaddingRight);
+                return itemView;
+            }
+        }
+    }
+
+    @Override
+    public boolean areAllItemsEnabled() {
+        // If "all" section is being loaded we'll show mLoadingView, which is not enabled.
+        // Otherwise check the all the other components in the ListView and return appropriate
+        // result.
+        return !mContactEntryListAdapter.isLoading()
+                && (mContactTileAdapter.areAllItemsEnabled()
+                && mAccountFilterHeaderContainer.isEnabled()
+                && mContactEntryListAdapter.areAllItemsEnabled());
+    }
+
+    @Override
+    public boolean isEnabled(int position) {
+        final int contactTileAdapterCount = mContactTileAdapter.getCount();
+        final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount();
+        if (position < contactTileAdapterCount) {  // For "tile" and "frequent" sections
+            return mContactTileAdapter.isEnabled(position);
+        } else if (position == contactTileAdapterCount) {  // For "all" section's account header
+            // This will be handled by View's onClick event instead of ListView's onItemClick event.
+            return false;
+        } else {  // For "all" section
+            if (mContactEntryListAdapter.isLoading()) {  // "All" section is being loaded.
+                return false;
+            } else {
+                // "-1" for mAccountFilterHeaderContainer
+                final int localPosition = position - contactTileAdapterCount - 1;
+                return mContactEntryListAdapter.isEnabled(localPosition);
+            }
+        }
+    }
+
+    @Override
+    public int getPositionForSection(int sectionIndex) {
+        final int contactTileAdapterCount = mContactTileAdapter.getCount();
+        final int localPosition = mContactEntryListAdapter.getPositionForSection(sectionIndex);
+        return contactTileAdapterCount + 1 + localPosition;
+    }
+
+    @Override
+    public int getSectionForPosition(int position) {
+        final int contactTileAdapterCount = mContactTileAdapter.getCount();
+        if (position <= contactTileAdapterCount) {
+            return 0;
+        } else {
+            // "-1" for mAccountFilterHeaderContainer
+            final int localPosition = position - contactTileAdapterCount - 1;
+            return mContactEntryListAdapter.getSectionForPosition(localPosition);
+        }
+    }
+
+    @Override
+    public Object[] getSections() {
+        return mContactEntryListAdapter.getSections();
+    }
+
+    public boolean shouldShowFirstScroller(int firstVisibleItem) {
+        final int contactTileAdapterCount = mContactTileAdapter.getCount();
+        return firstVisibleItem > contactTileAdapterCount;
+    }
+}
diff --git a/src/com/android/dialer/util/AsyncTaskExecutor.java b/src/com/android/dialer/util/AsyncTaskExecutor.java
new file mode 100644
index 0000000..ca09f08
--- /dev/null
+++ b/src/com/android/dialer/util/AsyncTaskExecutor.java
@@ -0,0 +1,48 @@
+/*
+ * 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.dialer.util;
+
+import android.os.AsyncTask;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Interface used to submit {@link AsyncTask} objects to run in the background.
+ * <p>
+ * This interface has a direct parallel with the {@link Executor} interface. It exists to decouple
+ * the mechanics of AsyncTask submission from the description of how that AsyncTask will execute.
+ * <p>
+ * One immediate benefit of this approach is that testing becomes much easier, since it is easy to
+ * introduce a mock or fake AsyncTaskExecutor in unit/integration tests, and thus inspect which
+ * tasks have been submitted and control their execution in an orderly manner.
+ * <p>
+ * Another benefit in due course will be the management of the submitted tasks. An extension to this
+ * interface is planned to allow Activities to easily cancel all the submitted tasks that are still
+ * pending in the onDestroy() method of the Activity.
+ */
+public interface AsyncTaskExecutor {
+    /**
+     * Executes the given AsyncTask with the default Executor.
+     * <p>
+     * This method <b>must only be called from the ui thread</b>.
+     * <p>
+     * The identifier supplied is any Object that can be used to identify the task later. Most
+     * commonly this will be an enum which the tests can also refer to. {@code null} is also
+     * accepted, though of course this won't help in identifying the task later.
+     */
+    <T> AsyncTask<T, ?, ?> submit(Object identifier, AsyncTask<T, ?, ?> task, T... params);
+}
diff --git a/src/com/android/dialer/util/AsyncTaskExecutors.java b/src/com/android/dialer/util/AsyncTaskExecutors.java
new file mode 100644
index 0000000..4f06e28
--- /dev/null
+++ b/src/com/android/dialer/util/AsyncTaskExecutors.java
@@ -0,0 +1,100 @@
+/*
+ * 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.dialer.util;
+
+import android.os.AsyncTask;
+import android.os.Looper;
+
+import com.android.contacts.test.NeededForTesting;
+import com.google.common.base.Preconditions;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Factory methods for creating AsyncTaskExecutors.
+ * <p>
+ * All of the factory methods on this class check first to see if you have set a static
+ * {@link AsyncTaskExecutorFactory} set through the
+ * {@link #setFactoryForTest(AsyncTaskExecutorFactory)} method, and if so delegate to that instead,
+ * which is one way of injecting dependencies for testing classes whose construction cannot be
+ * controlled such as {@link android.app.Activity}.
+ */
+public final class AsyncTaskExecutors {
+    /**
+     * A single instance of the {@link AsyncTaskExecutorFactory}, to which we delegate if it is
+     * non-null, for injecting when testing.
+     */
+    private static AsyncTaskExecutorFactory mInjectedAsyncTaskExecutorFactory = null;
+
+    /**
+     * Creates an AsyncTaskExecutor that submits tasks to run with
+     * {@link AsyncTask#SERIAL_EXECUTOR}.
+     */
+    public static AsyncTaskExecutor createAsyncTaskExecutor() {
+        synchronized (AsyncTaskExecutors.class) {
+            if (mInjectedAsyncTaskExecutorFactory != null) {
+                return mInjectedAsyncTaskExecutorFactory.createAsyncTaskExeuctor();
+            }
+            return new SimpleAsyncTaskExecutor(AsyncTask.SERIAL_EXECUTOR);
+        }
+    }
+
+    /**
+     * Creates an AsyncTaskExecutor that submits tasks to run with
+     * {@link AsyncTask#THREAD_POOL_EXECUTOR}.
+     */
+    public static AsyncTaskExecutor createThreadPoolExecutor() {
+        synchronized (AsyncTaskExecutors.class) {
+            if (mInjectedAsyncTaskExecutorFactory != null) {
+                return mInjectedAsyncTaskExecutorFactory.createAsyncTaskExeuctor();
+            }
+            return new SimpleAsyncTaskExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+        }
+    }
+
+    /** Interface for creating AsyncTaskExecutor objects. */
+    public interface AsyncTaskExecutorFactory {
+        AsyncTaskExecutor createAsyncTaskExeuctor();
+    }
+
+    @NeededForTesting
+    public static void setFactoryForTest(AsyncTaskExecutorFactory factory) {
+        synchronized (AsyncTaskExecutors.class) {
+            mInjectedAsyncTaskExecutorFactory = factory;
+        }
+    }
+
+    public static void checkCalledFromUiThread() {
+        Preconditions.checkState(Thread.currentThread() == Looper.getMainLooper().getThread(),
+                "submit method must be called from ui thread, was: " + Thread.currentThread());
+    }
+
+    private static class SimpleAsyncTaskExecutor implements AsyncTaskExecutor {
+        private final Executor mExecutor;
+
+        public SimpleAsyncTaskExecutor(Executor executor) {
+            mExecutor = executor;
+        }
+
+        @Override
+        public <T> AsyncTask<T, ?, ?> submit(Object identifer, AsyncTask<T, ?, ?> task,
+                T... params) {
+            checkCalledFromUiThread();
+            return task.executeOnExecutor(mExecutor, params);
+        }
+    }
+}
diff --git a/src/com/android/dialer/util/EmptyLoader.java b/src/com/android/dialer/util/EmptyLoader.java
new file mode 100644
index 0000000..dd4c0a3
--- /dev/null
+++ b/src/com/android/dialer/util/EmptyLoader.java
@@ -0,0 +1,60 @@
+/*
+ * 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.dialer.util;
+
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Context;
+import android.content.Loader;
+import android.os.Bundle;
+
+/**
+ * A {@link Loader} only used to make use of the {@link android.app.Fragment#setStartDeferred}
+ * feature from an old-style fragment which doesn't use {@link Loader}s to load data.
+ *
+ * This loader never delivers results.  A caller fragment must destroy it when deferred fragments
+ * should be started.
+ */
+public class EmptyLoader extends Loader<Object> {
+    public EmptyLoader(Context context) {
+        super(context);
+    }
+
+    /**
+     * {@link LoaderCallbacks} which just generates {@link EmptyLoader}.  {@link #onLoadFinished}
+     * and {@link #onLoaderReset} are no-op.
+     */
+    public static class Callback implements LoaderCallbacks<Object> {
+        private final Context mContext;
+
+        public Callback(Context context) {
+            mContext = context.getApplicationContext();
+        }
+
+        @Override
+        public Loader<Object> onCreateLoader(int id, Bundle args) {
+            return new EmptyLoader(mContext);
+        }
+
+        @Override
+        public void onLoadFinished(Loader<Object> loader, Object data) {
+        }
+
+        @Override
+        public void onLoaderReset(Loader<Object> loader) {
+        }
+    }
+}
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackFragment.java b/src/com/android/dialer/voicemail/VoicemailPlaybackFragment.java
index 473d40b..70580ba 100644
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackFragment.java
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackFragment.java
@@ -40,9 +40,9 @@
 import android.widget.TextView;
 
 import com.android.common.io.MoreCloseables;
-import com.android.contacts.ProximitySensorAware;
 import com.android.contacts.R;
-import com.android.contacts.util.AsyncTaskExecutors;
+import com.android.dialer.ProximitySensorAware;
+import com.android.dialer.util.AsyncTaskExecutors;
 import com.android.ex.variablespeed.MediaPlayerProxy;
 import com.android.ex.variablespeed.VariableSpeed;
 import com.google.common.base.Preconditions;
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
index 93b60de..c87e677 100644
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
@@ -31,7 +31,7 @@
 import android.widget.SeekBar;
 
 import com.android.contacts.R;
-import com.android.contacts.util.AsyncTaskExecutor;
+import com.android.dialer.util.AsyncTaskExecutor;
 import com.android.ex.variablespeed.MediaPlayerProxy;
 import com.android.ex.variablespeed.SingleThreadedMediaPlayerProxy;
 import com.google.common.annotations.VisibleForTesting;