Merge "Modifications to bold contacts' first names."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 83331da..ed03e77 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -336,6 +336,22 @@
             </intent-filter>
         </activity>
 
+
+        <!-- List of groups -->
+        <activity android:name=".activities.GroupBrowserActivity"
+            android:label="@string/contactsGroupsLabel"
+            android:theme="@style/ContactBrowserTheme"
+            android:launchMode="singleTop"
+            android:clearTaskOnLaunch="true">
+            <!-- TODO: Remove this temporary intent action name when the fragmentization
+                 work is done. -->
+            <intent-filter>
+                <action android:name="com.android.phone.action.GROUPS_LIST" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.TAB" />
+            </intent-filter>
+        </activity>
+
         <!-- Used to show QuickContact window over a translucent activity, which is a
              temporary hack until we add better framework support. -->
         <activity
diff --git a/res/layout-land/dialpad_activity.xml b/res/layout-land/dialpad_fragment.xml
similarity index 97%
rename from res/layout-land/dialpad_activity.xml
rename to res/layout-land/dialpad_fragment.xml
index 985d047..fe9fb28 100644
--- a/res/layout-land/dialpad_activity.xml
+++ b/res/layout-land/dialpad_fragment.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2006 The Android Open Source Project
+<!-- 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.
diff --git a/res/layout-long-land/dialpad_activity.xml b/res/layout-long-land/dialpad_fragment.xml
similarity index 97%
rename from res/layout-long-land/dialpad_activity.xml
rename to res/layout-long-land/dialpad_fragment.xml
index 1cf9690..f287741 100644
--- a/res/layout-long-land/dialpad_activity.xml
+++ b/res/layout-long-land/dialpad_fragment.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!-- 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.
diff --git a/res/layout-long/dialpad_activity.xml b/res/layout-long/dialpad_fragment.xml
similarity index 97%
rename from res/layout-long/dialpad_activity.xml
rename to res/layout-long/dialpad_fragment.xml
index 2580625..85250e2 100644
--- a/res/layout-long/dialpad_activity.xml
+++ b/res/layout-long/dialpad_fragment.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!-- 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.
diff --git a/res/layout-xlarge/account_selector_list_item.xml b/res/layout-xlarge/account_selector_list_item.xml
new file mode 100644
index 0000000..38acfc5
--- /dev/null
+++ b/res/layout-xlarge/account_selector_list_item.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+>
+    <ImageView android:id="@android:id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="6dip"
+        android:layout_centerVertical="true"
+    />
+
+    <TextView android:id="@android:id/text1"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="6dip"
+        android:layout_marginTop="6dip"
+        android:layout_toRightOf="@android:id/icon"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+    />
+
+    <TextView android:id="@android:id/text2"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@android:id/text1"
+        android:layout_alignLeft="@android:id/text1"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+    />
+</RelativeLayout>
diff --git a/res/layout/account_selector_list_item.xml b/res/layout/account_selector_list_item.xml
index 38acfc5..6fe2a50 100644
--- a/res/layout/account_selector_list_item.xml
+++ b/res/layout/account_selector_list_item.xml
@@ -17,29 +17,23 @@
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:minHeight="?android:attr/listPreferredItemHeight"
->
-    <ImageView android:id="@android:id/icon"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginLeft="6dip"
-        android:layout_centerVertical="true"
-    />
+    android:paddingLeft="@dimen/account_selector_horizontal_margin"
+    android:paddingRight="@dimen/account_selector_horizontal_margin"
+    android:minHeight="@dimen/account_selector_min_item_height" >
 
     <TextView android:id="@android:id/text1"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginLeft="6dip"
-        android:layout_marginTop="6dip"
-        android:layout_toRightOf="@android:id/icon"
-        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:layout_alignParentLeft="true"
+        android:layout_toLeftOf="@android:id/icon"
+        android:layout_centerVertical="true"
+        android:textAppearance="?android:attr/textAppearanceMedium"
     />
 
-    <TextView android:id="@android:id/text2"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_below="@android:id/text1"
-        android:layout_alignLeft="@android:id/text1"
-        android:textAppearance="?android:attr/textAppearanceSmall"
+    <ImageView android:id="@android:id/icon"
+        android:layout_width="@dimen/account_selector_icon_size"
+        android:layout_height="@dimen/account_selector_icon_size"
+        android:layout_alignParentRight="true"
+        android:layout_centerVertical="true"
     />
 </RelativeLayout>
diff --git a/res/layout/dialpad_activity.xml b/res/layout/dialpad_activity.xml
index 2cae9ae..93e9523 100644
--- a/res/layout/dialpad_activity.xml
+++ b/res/layout/dialpad_activity.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2006 The Android Open Source Project
+<!-- 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.
@@ -14,52 +14,12 @@
      limitations under the License.
 -->
 
-<!-- TODO (stopship) We don't want to specify a background color here. For now we just
-keep it because otherwise the dialer needs some imagination to use (white on white) -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/top"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:background="@android:color/black"
 >
-
-    <!-- Text field above the keypad where the digits are displayed.
-         It's type is set to NULL (to disable the IME keyboard) in the
-         java code.
-    -->
-    <!-- TODO: Use a textAppearance to control the display of the number -->
-    <EditText android:id="@+id/digits"
-        android:layout_width="match_parent"
-        android:layout_height="67dip"
-        android:gravity="center"
-        android:maxLines="1"
-        android:scrollHorizontally="true"
-        android:textSize="33sp"
-        android:freezesText="true"
-        android:background="@drawable/btn_dial_textfield"
-        android:textColor="@color/dialer_button_text"
-        android:focusableInTouchMode="true"
-        android:editable="true"
-        android:cursorVisible="false"
-        android:layout_weight="0"
-        android:contentDescription="@string/description_digits_edittext"
-    />
-
-    <!-- Keypad section -->
-    <include layout="@layout/dialpad" />
-
-    <!-- Horizontal row of buttons (Voicemail + DialButton + Delete.) -->
-    <include layout="@layout/voicemail_dial_delete" />
-
-    <!-- "Dialpad chooser" UI, shown only when the user brings up the
-         Dialer while a call is already in progress.
-         When this UI is visible, the other Dialer elements
-         (the textfield/button and the dialpad) are hidden. -->
-    <ListView android:id="@+id/dialpadChooser"
-        android:layout_width="match_parent"
-        android:layout_height="1dip"
-        android:layout_weight="1"
-    />
-
-</LinearLayout>
+    <fragment class="com.android.contacts.dialpad.DialpadFragment"
+            android:id="@+id/dialpad_fragment"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+</FrameLayout>
diff --git a/res/layout-long/dialpad_activity.xml b/res/layout/dialpad_fragment.xml
similarity index 91%
copy from res/layout-long/dialpad_activity.xml
copy to res/layout/dialpad_fragment.xml
index 2580625..c516bd8 100644
--- a/res/layout-long/dialpad_activity.xml
+++ b/res/layout/dialpad_fragment.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
+<!-- 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.
@@ -31,11 +31,11 @@
     <!-- TODO: Use a textAppearance to control the display of the number -->
     <EditText android:id="@+id/digits"
         android:layout_width="match_parent"
-        android:layout_height="74dip"
+        android:layout_height="67dip"
         android:gravity="center"
         android:maxLines="1"
         android:scrollHorizontally="true"
-        android:textSize="34sp"
+        android:textSize="33sp"
         android:freezesText="true"
         android:background="@drawable/btn_dial_textfield"
         android:textColor="@color/dialer_button_text"
@@ -43,6 +43,7 @@
         android:editable="true"
         android:cursorVisible="false"
         android:layout_weight="0"
+        android:contentDescription="@string/description_digits_edittext"
     />
 
     <!-- Keypad section -->
diff --git a/res/layout/group_browse_list_fragment.xml b/res/layout/group_browse_list_fragment.xml
new file mode 100644
index 0000000..50c02c8
--- /dev/null
+++ b/res/layout/group_browse_list_fragment.xml
@@ -0,0 +1,39 @@
+<?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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <ListView
+      android:id="@+id/list"
+      android:layout_width="match_parent"
+      android:layout_height="0dip"
+      android:fastScrollEnabled="true"
+      android:scrollbarStyle="outsideOverlay"
+      android:layout_weight="1" />
+
+   <TextView
+     android:id="@+id/empty"
+     android:layout_width="match_parent"
+     android:layout_height="match_parent"
+     android:gravity="center"
+     android:text="@string/noGroups"
+     android:visibility="gone"/>
+
+</LinearLayout>
diff --git a/res/layout/group_browse_list_item.xml b/res/layout/group_browse_list_item.xml
new file mode 100644
index 0000000..accbedd
--- /dev/null
+++ b/res/layout/group_browse_list_item.xml
@@ -0,0 +1,60 @@
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <ImageView
+        android:id="@+id/icon"
+        android:scaleType="center"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_marginLeft="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_gravity="center_vertical" />
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:paddingTop="5dip"
+        android:paddingBottom="5dip">
+
+        <TextView
+            android:id="@+id/label"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:gravity="center_vertical"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:ellipsize="end"
+            android:singleLine="true" />
+
+        <TextView
+            android:id="@+id/account"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:gravity="center_vertical"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?android:attr/textColorSecondary"
+            android:ellipsize="end"
+            android:singleLine="true"
+            android:textStyle="italic" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout/group_browser_activity.xml b/res/layout/group_browser_activity.xml
new file mode 100644
index 0000000..f7187d9
--- /dev/null
+++ b/res/layout/group_browser_activity.xml
@@ -0,0 +1,28 @@
+<?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">
+
+    <fragment
+        android:id="@+id/list_fragment"
+        class="com.android.contacts.group.GroupBrowseListFragment"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+    />
+
+</FrameLayout>
diff --git a/res/layout/raw_contact_editor_view.xml b/res/layout/raw_contact_editor_view.xml
index 7c027eb..5aa1e95 100644
--- a/res/layout/raw_contact_editor_view.xml
+++ b/res/layout/raw_contact_editor_view.xml
@@ -32,7 +32,7 @@
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
             android:layout_marginRight="4dip"
-            android:background="@color/account_selection_background"
+            android:background="@android:drawable/list_selector_background"
             android:orientation="horizontal"
             android:gravity="left|center_vertical">
 
@@ -49,10 +49,13 @@
 
             <LinearLayout
                 android:id="@+id/account"
-                android:layout_height="wrap_content"
+                android:layout_height="match_parent"
                 android:layout_width="match_parent"
+                android:paddingLeft="@dimen/account_selector_horizontal_margin"
+                android:paddingRight="@dimen/account_selector_horizontal_margin"
                 android:background="@color/account_selection_background"
-                android:orientation="vertical">
+                android:orientation="vertical"
+                android:gravity="left|center_vertical">
                 <TextView
                     android:id="@+id/account_type"
                     android:layout_width="wrap_content"
@@ -78,8 +81,8 @@
 
                     <ImageView
                          android:id="@+id/account_icon"
-                         android:layout_width="wrap_content"
-                         android:layout_height="wrap_content"  />
+                         android:layout_width="@dimen/account_selector_icon_size"
+                         android:layout_height="@dimen/account_selector_icon_size"  />
                 </LinearLayout>
             </LinearLayout>
         </LinearLayout>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 354bec7..ff7d873 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -26,6 +26,9 @@
     <dimen name="aggregation_suggestion_icon_size">40dip</dimen>
 
     <dimen name="account_selector_popup_width">400dip</dimen>
+    <dimen name="account_selector_icon_size">30dip</dimen>
+    <dimen name="account_selector_min_item_height">48dip</dimen>
+    <dimen name="account_selector_horizontal_margin">6dip</dimen>
 
     <dimen name="photo_action_popup_width">400dip</dimen>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b531431..5605d25 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -264,6 +264,9 @@
     <!-- The text displayed when the contacts list is empty while displaying all contacts -->
     <string name="noContacts">No contacts.</string>
 
+    <!-- The text displayed when the groups list is empty while displaying all groups [CHAR LIMIT=NONE] -->
+    <string name="noGroups">No groups.</string>
+
     <!-- The text displayed when the contacts list is empty while displaying results after searching contacts -->
     <string name="noMatchingContacts">No matching contacts found.</string>
 
@@ -349,6 +352,9 @@
     <!-- The description text for the contacts tab. Space is limited for this string, so the shorter the better -->
     <string name="contactsIconLabel">Contacts</string>
 
+    <!-- The description text for the groups tab. Space is limited for this string, so the shorter the better -->
+    <string name="contactsGroupsLabel">Groups</string>
+
     <!-- The description text for the favorites tab. Space is limited for this string, so the shorter the better -->
     <string name="contactsFavoritesLabel">Favorites</string>
 
diff --git a/src/com/android/contacts/activities/DialpadActivity.java b/src/com/android/contacts/activities/DialpadActivity.java
index cfbdff2..a30460a 100644
--- a/src/com/android/contacts/activities/DialpadActivity.java
+++ b/src/com/android/contacts/activities/DialpadActivity.java
@@ -17,12 +17,8 @@
 package com.android.contacts.activities;
 
 import com.android.contacts.ContactsSearchManager;
-import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
-import com.android.contacts.SpecialCharSequenceMgr;
-import com.android.internal.telephony.ITelephony;
-import com.android.phone.CallLogAsync;
-import com.android.phone.HapticFeedback;
+import com.android.contacts.dialpad.DialpadFragment;
 
 import android.app.Activity;
 import android.content.ActivityNotFoundException;
@@ -30,243 +26,48 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.Drawable;
-import android.media.AudioManager;
-import android.media.ToneGenerator;
-import android.net.Uri;
 import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.provider.Settings;
-import android.provider.Contacts.People;
-import android.provider.Contacts.Phones;
-import android.provider.Contacts.PhonesColumns;
-import android.provider.Contacts.Intents.Insert;
-import android.telephony.PhoneNumberFormattingTextWatcher;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.text.method.DialerKeyListener;
 import android.util.Log;
 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.Window;
 import android.view.inputmethod.InputMethodManager;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.EditText;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.TextView;
 
 /**
- * Dialer activity that displays the typical twelve key interface.
+ * Activity that displays a twelve-key phone dialpad.
+ * This is just a simple container around DialpadFragment.
+ * @see DialpadFragment
  */
-@SuppressWarnings("deprecation")
-public class DialpadActivity extends Activity implements View.OnClickListener,
-        View.OnLongClickListener, View.OnKeyListener,
-        AdapterView.OnItemClickListener, TextWatcher {
-    private static final String EMPTY_NUMBER = "";
+public class DialpadActivity extends Activity {
     private static final String TAG = "DialpadActivity";
 
-    /** The length of DTMF tones in milliseconds */
-    private static final int TONE_LENGTH_MS = 150;
-
-    /** The DTMF tone volume relative to other sounds in the stream */
-    private static final int TONE_RELATIVE_VOLUME = 80;
-
-    /** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */
-    private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-    private EditText mDigits;
-    private View mDelete;
-    private MenuItem mAddToContactMenuItem;
-    private ToneGenerator mToneGenerator;
-    private Object mToneGeneratorLock = new Object();
-    private Drawable mDigitsBackground;
-    private Drawable mDigitsEmptyBackground;
-    private View mDialpad;
-    private View mVoicemailDialAndDeleteRow;
-    private View mVoicemailButton;
-    private View mDialButton;
-    private ListView mDialpadChooser;
-    private DialpadChooserAdapter mDialpadChooserAdapter;
-    //Member variables for dialpad options
-    private MenuItem m2SecPauseMenuItem;
-    private MenuItem mWaitMenuItem;
-    private static final int MENU_ADD_CONTACTS = 1;
-    private static final int MENU_2S_PAUSE = 2;
-    private static final int MENU_WAIT = 3;
-
-    // Last number dialed, retrieved asynchronously from the call DB
-    // in onCreate. This number is displayed when the user hits the
-    // send key and cleared in onPause.
-    CallLogAsync mCallLog = new CallLogAsync();
-    private String mLastNumberDialed = EMPTY_NUMBER;
-
-    // determines if we want to playback local DTMF tones.
-    private boolean mDTMFToneEnabled;
-
-    // Vibration (haptic feedback) for dialer key presses.
-    private HapticFeedback mHaptic = new HapticFeedback();
-
-    /** Identifier for the "Add Call" intent extra. */
-    static final String ADD_CALL_MODE_KEY = "add_call_mode";
-
-    /**
-     * Identifier for intent extra for sending an empty Flash message for
-     * CDMA networks. This message is used by the network to simulate a
-     * press/depress of the "hookswitch" of a landline phone. Aka "empty flash".
-     *
-     * TODO: Using an intent extra to tell the phone to send this flash is a
-     * temporary measure. To be replaced with an ITelephony call in the future.
-     * TODO: Keep in sync with the string defined in OutgoingCallBroadcaster.java
-     * in Phone app until this is replaced with the ITelephony API.
-     */
-    static final String EXTRA_SEND_EMPTY_FLASH
-            = "com.android.phone.extra.SEND_EMPTY_FLASH";
-
-    /** Indicates if we are opening this dialer to add a call from the InCallScreen. */
-    private boolean mIsAddCallMode;
-
-    private String mCurrentCountryIso;
-
-    PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
-            /**
-             * Listen for phone state changes so that we can take down the
-             * "dialpad chooser" if the phone becomes idle while the
-             * chooser UI is visible.
-             */
-            @Override
-            public void onCallStateChanged(int state, String incomingNumber) {
-                // Log.i(TAG, "PhoneStateListener.onCallStateChanged: "
-                //       + state + ", '" + incomingNumber + "'");
-                if ((state == TelephonyManager.CALL_STATE_IDLE) && dialpadChooserVisible()) {
-                    // Log.i(TAG, "Call ended with dialpad chooser visible!  Taking it down...");
-                    // Note there's a race condition in the UI here: the
-                    // dialpad chooser could conceivably disappear (on its
-                    // own) at the exact moment the user was trying to select
-                    // one of the choices, which would be confusing.  (But at
-                    // least that's better than leaving the dialpad chooser
-                    // onscreen, but useless...)
-                    showDialpadChooser(false);
-                }
-            }
-        };
-
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-        // Do nothing
-    }
-
-    public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
-        // Do nothing
-        // DTMF Tones do not need to be played here any longer -
-        // the DTMF dialer handles that functionality now.
-    }
-
-    public void afterTextChanged(Editable input) {
-        if (SpecialCharSequenceMgr.handleChars(this, input.toString(), mDigits)) {
-            // A special sequence was entered, clear the digits
-            mDigits.getText().clear();
-        }
-
-        if (!isDigitsEmpty()) {
-            mDigits.setBackgroundDrawable(mDigitsBackground);
-        } else {
-            mDigits.setCursorVisible(false);
-            mDigits.setBackgroundDrawable(mDigitsEmptyBackground);
-        }
-
-        updateDialAndDeleteButtonEnabledState();
-    }
+    private DialpadFragment mFragment;
 
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
-        mCurrentCountryIso = ContactsUtils.getCurrentCountryIso(this);
+        setContentView(R.layout.dialpad_activity);
+
         Resources r = getResources();
         // Do not show title in the case the device is in carmode.
         if ((r.getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK) ==
                 Configuration.UI_MODE_TYPE_CAR) {
             requestWindowFeature(Window.FEATURE_NO_TITLE);
         }
-        // Set the content view
-        setContentView(getContentViewResource());
 
-        // Load up the resources for the text field.
-        mDigitsBackground = r.getDrawable(R.drawable.btn_dial_textfield_active);
-        mDigitsEmptyBackground = r.getDrawable(R.drawable.btn_dial_textfield);
+        mFragment = (DialpadFragment) getFragmentManager().findFragmentById(
+                R.id.dialpad_fragment);
 
-        mDigits = (EditText) findViewById(R.id.digits);
-        mDigits.setKeyListener(DialerKeyListener.getInstance());
-        mDigits.setOnClickListener(this);
-        mDigits.setOnKeyListener(this);
-
-        maybeAddNumberFormatting();
-
-        // Check for the presence of the keypad
-        View view = findViewById(R.id.one);
-        if (view != null) {
-            setupKeypad();
-        }
-
-        mVoicemailDialAndDeleteRow = findViewById(R.id.voicemailAndDialAndDelete);
-
-        initVoicemailButton();
-
-        // Check whether we should show the onscreen "Dial" button.
-        mDialButton = mVoicemailDialAndDeleteRow.findViewById(R.id.dialButton);
-
-        if (r.getBoolean(R.bool.config_show_onscreen_dial_button)) {
-            mDialButton.setOnClickListener(this);
-        } else {
-            mDialButton.setVisibility(View.GONE); // It's VISIBLE by default
-            mDialButton = null;
-        }
-
-        view = mVoicemailDialAndDeleteRow.findViewById(R.id.deleteButton);
-        view.setOnClickListener(this);
-        view.setOnLongClickListener(this);
-        mDelete = view;
-
-        mDialpad = findViewById(R.id.dialpad);  // This is null in landscape mode.
-
-        // In landscape we put the keyboard in phone mode.
-        // In portrait we prevent the soft keyboard to show since the
-        // dialpad acts as one already.
-        if (null == mDialpad) {
-            mDigits.setInputType(android.text.InputType.TYPE_CLASS_PHONE);
-        } else {
-            mDigits.setInputType(android.text.InputType.TYPE_NULL);
-        }
-
-        // Set up the "dialpad chooser" UI; see showDialpadChooser().
-        mDialpadChooser = (ListView) findViewById(R.id.dialpadChooser);
-        mDialpadChooser.setOnItemClickListener(this);
-
-        if (!resolveIntent() && icicle != null) {
+        // Manually run the onRestoreInstanceState() sequence here, but only if
+        // our intent does *not* have the DialtactsActivity.EXTRA_IGNORE_STATE
+        // set (see the references to EXTRA_IGNORE_STATE in DialtactsActivity).
+        // TODO: Find a cleaner way of doing this.
+        if (!mFragment.resolveIntent() && (icicle != null)) {
             super.onRestoreInstanceState(icicle);
         }
-
-        try {
-            mHaptic.init(this, r.getBoolean(R.bool.config_enable_dialer_key_vibration));
-        } catch (Resources.NotFoundException nfe) {
-             Log.e(TAG, "Vibrate control bool missing.", nfe);
-        }
-
     }
 
     @Override
@@ -274,222 +75,22 @@
         // Do nothing, state is restored in onCreate() if needed
     }
 
-    protected void maybeAddNumberFormatting() {
-        mDigits.addTextChangedListener(new PhoneNumberFormattingTextWatcher(mCurrentCountryIso));
-    }
-
-    /**
-     * Overridden by subclasses to control the resource used by the content view.
-     */
-    protected int getContentViewResource() {
-        return R.layout.dialpad_activity;
-    }
-
-    private boolean resolveIntent() {
-        boolean ignoreState = false;
-
-        // Find the proper intent
-        final Intent intent;
-        if (isChild()) {
-            intent = getParent().getIntent();
-            ignoreState = intent.getBooleanExtra(DialtactsActivity.EXTRA_IGNORE_STATE, false);
-        } else {
-            intent = getIntent();
-        }
-        // Log.i(TAG, "==> resolveIntent(): intent: " + intent);
-
-        // by default we are not adding a call.
-        mIsAddCallMode = false;
-
-        // By default we don't show the "dialpad chooser" UI.
-        boolean needToShowDialpadChooser = false;
-
-        // Resolve the intent
-        final String action = intent.getAction();
-        if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
-            // see if we are "adding a call" from the InCallScreen; false by default.
-            mIsAddCallMode = intent.getBooleanExtra(ADD_CALL_MODE_KEY, false);
-
-            Uri uri = intent.getData();
-            if (uri != null) {
-                if ("tel".equals(uri.getScheme())) {
-                    // Put the requested number into the input area
-                    String data = uri.getSchemeSpecificPart();
-                    setFormattedDigits(data, null);
-                } else {
-                    String type = intent.getType();
-                    if (People.CONTENT_ITEM_TYPE.equals(type)
-                            || Phones.CONTENT_ITEM_TYPE.equals(type)) {
-                        // Query the phone number
-                        Cursor c = getContentResolver().query(intent.getData(),
-                                new String[] {PhonesColumns.NUMBER, PhonesColumns.NUMBER_KEY},
-                                null, null, null);
-                        if (c != null) {
-                            if (c.moveToFirst()) {
-                                // Put the number into the input area
-                                setFormattedDigits(c.getString(0), c.getString(1));
-                            }
-                            c.close();
-                        }
-                    }
-                }
-            } else {
-                // ACTION_DIAL or ACTION_VIEW with no data.
-                // This behaves basically like ACTION_MAIN: If there's
-                // already an active call, bring up an intermediate UI to
-                // make the user confirm what they really want to do.
-                // Be sure *not* to show the dialpad chooser if this is an
-                // explicit "Add call" action, though.
-                if (!mIsAddCallMode && phoneIsInUse()) {
-                    needToShowDialpadChooser = true;
-                }
-            }
-        } else if (Intent.ACTION_MAIN.equals(action)) {
-            // The MAIN action means we're bringing up a blank dialer
-            // (e.g. by selecting the Home shortcut, or tabbing over from
-            // Contacts or Call log.)
-            //
-            // At this point, IF there's already an active call, there's a
-            // good chance that the user got here accidentally (but really
-            // wanted the in-call dialpad instead).  So we bring up an
-            // intermediate UI to make the user confirm what they really
-            // want to do.
-            if (phoneIsInUse()) {
-                // Log.i(TAG, "resolveIntent(): phone is in use; showing dialpad chooser!");
-                needToShowDialpadChooser = true;
-            }
-        }
-
-        // Bring up the "dialpad chooser" IFF we need to make the user
-        // confirm which dialpad they really want.
-        showDialpadChooser(needToShowDialpadChooser);
-
-        return ignoreState;
-    }
-
-    protected void setFormattedDigits(String data, String normalizedNumber) {
-        // strip the non-dialable numbers out of the data string.
-        String dialString = PhoneNumberUtils.extractNetworkPortion(data);
-        dialString =
-                PhoneNumberUtils.formatNumber(dialString, normalizedNumber, mCurrentCountryIso);
-        if (!TextUtils.isEmpty(dialString)) {
-            Editable digits = mDigits.getText();
-            digits.replace(0, digits.length(), dialString);
-            // for some reason this isn't getting called in the digits.replace call above..
-            // but in any case, this will make sure the background drawable looks right
-            afterTextChanged(digits);
-        }
-    }
-
     @Override
     protected void onNewIntent(Intent newIntent) {
         setIntent(newIntent);
-        resolveIntent();
+        mFragment.resolveIntent();
     }
 
     @Override
     protected void onPostCreate(Bundle savedInstanceState) {
         super.onPostCreate(savedInstanceState);
 
-        // This can't be done in onCreate(), since the auto-restoring of the digits
-        // will play DTMF tones for all the old digits if it is when onRestoreSavedInstanceState()
-        // is called. This method will be called every time the activity is created, and
-        // will always happen after onRestoreSavedInstanceState().
-        mDigits.addTextChangedListener(this);
+        // Pass this lifecycle event down to the fragment
+        mFragment.onPostCreate();
     }
 
-    private void setupKeypad() {
-        // Setup the listeners for the buttons
-        View view = findViewById(R.id.one);
-        view.setOnClickListener(this);
-        view.setOnLongClickListener(this);
-
-        findViewById(R.id.two).setOnClickListener(this);
-        findViewById(R.id.three).setOnClickListener(this);
-        findViewById(R.id.four).setOnClickListener(this);
-        findViewById(R.id.five).setOnClickListener(this);
-        findViewById(R.id.six).setOnClickListener(this);
-        findViewById(R.id.seven).setOnClickListener(this);
-        findViewById(R.id.eight).setOnClickListener(this);
-        findViewById(R.id.nine).setOnClickListener(this);
-        findViewById(R.id.star).setOnClickListener(this);
-
-        view = findViewById(R.id.zero);
-        view.setOnClickListener(this);
-        view.setOnLongClickListener(this);
-
-        findViewById(R.id.pound).setOnClickListener(this);
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-
-        // Query the last dialed number. Do it first because hitting
-        // the DB is 'slow'. This call is asynchronous.
-        queryLastOutgoingCall();
-
-        // retrieve the DTMF tone play back setting.
-        mDTMFToneEnabled = Settings.System.getInt(getContentResolver(),
-                Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
-
-        // Retrieve the haptic feedback setting.
-        mHaptic.checkSystemSetting();
-
-        // if the mToneGenerator creation fails, just continue without it.  It is
-        // a local audio signal, and is not as important as the dtmf tone itself.
-        synchronized(mToneGeneratorLock) {
-            if (mToneGenerator == null) {
-                try {
-                    // we want the user to be able to control the volume of the dial tones
-                    // outside of a call, so we use the stream type that is also mapped to the
-                    // volume control keys for this activity
-                    mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME);
-                    setVolumeControlStream(DIAL_TONE_STREAM_TYPE);
-                } catch (RuntimeException e) {
-                    Log.w(TAG, "Exception caught while creating local tone generator: " + e);
-                    mToneGenerator = null;
-                }
-            }
-        }
-
-        Activity parent = getParent();
-        // See if we were invoked with a DIAL intent. If we were, fill in the appropriate
-        // digits in the dialer field.
-        if (parent != null && parent instanceof DialtactsActivity) {
-            Uri dialUri = ((DialtactsActivity) parent).getAndClearDialUri();
-            if (dialUri != null) {
-                resolveIntent();
-            }
-        }
-
-        // While we're in the foreground, listen for phone state changes,
-        // purely so that we can take down the "dialpad chooser" if the
-        // phone becomes idle while the chooser UI is visible.
-        TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
-        telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
-
-        // Potentially show hint text in the mDigits field when the user
-        // hasn't typed any digits yet.  (If there's already an active call,
-        // this hint text will remind the user that he's about to add a new
-        // call.)
-        //
-        // TODO: consider adding better UI for the case where *both* lines
-        // are currently in use.  (Right now we let the user try to add
-        // another call, but that call is guaranteed to fail.  Perhaps the
-        // entire dialer UI should be disabled instead.)
-        if (phoneIsInUse()) {
-            mDigits.setHint(R.string.dialerDialpadHintText);
-        } else {
-            // Common case; no hint necessary.
-            mDigits.setHint(null);
-
-            // Also, a sanity-check: the "dialpad chooser" UI should NEVER
-            // be visible if the phone is idle!
-            showDialpadChooser(false);
-        }
-
-        updateDialAndDeleteButtonEnabledState();
+    public DialpadFragment getFragment() {
+        return mFragment;
     }
 
     @Override
@@ -501,96 +102,33 @@
             // have a window token yet in onCreate / onNewIntent
             InputMethodManager inputMethodManager = (InputMethodManager)
                     getSystemService(Context.INPUT_METHOD_SERVICE);
-            inputMethodManager.hideSoftInputFromWindow(mDigits.getWindowToken(), 0);
+            inputMethodManager.hideSoftInputFromWindow(
+                    mFragment.getDigitsWidget().getWindowToken(), 0);
         }
     }
 
     @Override
-    protected void onPause() {
-        super.onPause();
-
-        // Stop listening for phone state changes.
-        TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
-        telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
-
-        synchronized(mToneGeneratorLock) {
-            if (mToneGenerator != null) {
-                mToneGenerator.release();
-                mToneGenerator = null;
-            }
-        }
-        // TODO: I wonder if we should not check if the AsyncTask that
-        // lookup the last dialed number has completed.
-        mLastNumberDialed = EMPTY_NUMBER;  // Since we are going to query again, free stale number.
-    }
-
-    @Override
     public boolean onCreateOptionsMenu(Menu menu) {
-        mAddToContactMenuItem = menu.add(0, MENU_ADD_CONTACTS, 0, R.string.recentCalls_addToContact)
-                .setIcon(android.R.drawable.ic_menu_add);
-        m2SecPauseMenuItem = menu.add(0, MENU_2S_PAUSE, 0, R.string.add_2sec_pause)
-                .setIcon(R.drawable.ic_menu_2sec_pause);
-        mWaitMenuItem = menu.add(0, MENU_WAIT, 0, R.string.add_wait)
-                .setIcon(R.drawable.ic_menu_wait);
+        super.onCreateOptionsMenu(menu);
+
+        // Nothing to do here; see DialpadFragment.onCreateOptionsMenu().
         return true;
     }
 
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
-        // We never show a menu if the "choose dialpad" UI is up.
-        if (dialpadChooserVisible()) {
+        // The DialpadFragment completely owns the options menu,
+        // so we don't add any items here.  We *do* however
+        // have to return false here if the DialpadFragment
+        // says there shouldn't be a menu at all.
+        if (!mFragment.allowOptionsMenu()) {
             return false;
         }
 
-        if (isDigitsEmpty()) {
-            mAddToContactMenuItem.setVisible(false);
-            m2SecPauseMenuItem.setVisible(false);
-            mWaitMenuItem.setVisible(false);
-        } else {
-            CharSequence digits = mDigits.getText();
+        super.onPrepareOptionsMenu(menu);
 
-            // Put the current digits string into an intent
-            Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
-            intent.putExtra(Insert.PHONE, digits);
-            intent.setType(People.CONTENT_ITEM_TYPE);
-            mAddToContactMenuItem.setIntent(intent);
-            mAddToContactMenuItem.setVisible(true);
-
-            // Check out whether to show Pause & Wait option menu items
-            int selectionStart;
-            int selectionEnd;
-            String strDigits = digits.toString();
-
-            selectionStart = mDigits.getSelectionStart();
-            selectionEnd = mDigits.getSelectionEnd();
-
-            if (selectionStart != -1) {
-                if (selectionStart > selectionEnd) {
-                    // swap it as we want start to be less then end
-                    int tmp = selectionStart;
-                    selectionStart = selectionEnd;
-                    selectionEnd = tmp;
-                }
-
-                if (selectionStart != 0) {
-                    // Pause can be visible if cursor is not in the begining
-                    m2SecPauseMenuItem.setVisible(true);
-
-                    // For Wait to be visible set of condition to meet
-                    mWaitMenuItem.setVisible(showWait(selectionStart,
-                                                      selectionEnd, strDigits));
-                } else {
-                    // cursor in the beginning both pause and wait to be invisible
-                    m2SecPauseMenuItem.setVisible(false);
-                    mWaitMenuItem.setVisible(false);
-                }
-            } else {
-                // cursor is not selected so assume new digit is added to the end
-                int strLength = strDigits.length();
-                mWaitMenuItem.setVisible(showWait(strLength,
-                                                      strLength, strDigits));
-            }
-        }
+        // See DialpadFragment.onPrepareOptionsMenu() for the actual menu
+        // contents.
         return true;
     }
 
@@ -606,6 +144,7 @@
                     try {
                         startActivity(intent);
                     } catch (ActivityNotFoundException e) {
+                        Log.w(TAG, "Failed to launch voice dialer: " + e);
                     }
                 }
                 return true;
@@ -614,7 +153,7 @@
                 long timeDiff = SystemClock.uptimeMillis() - event.getDownTime();
                 if (timeDiff >= ViewConfiguration.getLongPressTimeout()) {
                     // Long press detected, call voice mail
-                    callVoicemail();
+                    mFragment.callVoicemail();
                 }
                 return true;
             }
@@ -626,631 +165,13 @@
     public boolean onKeyUp(int keyCode, KeyEvent event) {
         switch (keyCode) {
             case KeyEvent.KEYCODE_CALL: {
-                dialButtonPressed();
+                mFragment.dialButtonPressed();
                 return true;
             }
         }
         return super.onKeyUp(keyCode, event);
     }
 
-    private void keyPressed(int keyCode) {
-        mHaptic.vibrate();
-        KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
-        mDigits.onKeyDown(keyCode, event);
-    }
-
-    public boolean onKey(View view, int keyCode, KeyEvent event) {
-        switch (view.getId()) {
-            case R.id.digits:
-                if (keyCode == KeyEvent.KEYCODE_ENTER) {
-                    dialButtonPressed();
-                    return true;
-                }
-                break;
-        }
-        return false;
-    }
-
-    public void onClick(View view) {
-        switch (view.getId()) {
-            case R.id.one: {
-                playTone(ToneGenerator.TONE_DTMF_1);
-                keyPressed(KeyEvent.KEYCODE_1);
-                return;
-            }
-            case R.id.two: {
-                playTone(ToneGenerator.TONE_DTMF_2);
-                keyPressed(KeyEvent.KEYCODE_2);
-                return;
-            }
-            case R.id.three: {
-                playTone(ToneGenerator.TONE_DTMF_3);
-                keyPressed(KeyEvent.KEYCODE_3);
-                return;
-            }
-            case R.id.four: {
-                playTone(ToneGenerator.TONE_DTMF_4);
-                keyPressed(KeyEvent.KEYCODE_4);
-                return;
-            }
-            case R.id.five: {
-                playTone(ToneGenerator.TONE_DTMF_5);
-                keyPressed(KeyEvent.KEYCODE_5);
-                return;
-            }
-            case R.id.six: {
-                playTone(ToneGenerator.TONE_DTMF_6);
-                keyPressed(KeyEvent.KEYCODE_6);
-                return;
-            }
-            case R.id.seven: {
-                playTone(ToneGenerator.TONE_DTMF_7);
-                keyPressed(KeyEvent.KEYCODE_7);
-                return;
-            }
-            case R.id.eight: {
-                playTone(ToneGenerator.TONE_DTMF_8);
-                keyPressed(KeyEvent.KEYCODE_8);
-                return;
-            }
-            case R.id.nine: {
-                playTone(ToneGenerator.TONE_DTMF_9);
-                keyPressed(KeyEvent.KEYCODE_9);
-                return;
-            }
-            case R.id.zero: {
-                playTone(ToneGenerator.TONE_DTMF_0);
-                keyPressed(KeyEvent.KEYCODE_0);
-                return;
-            }
-            case R.id.pound: {
-                playTone(ToneGenerator.TONE_DTMF_P);
-                keyPressed(KeyEvent.KEYCODE_POUND);
-                return;
-            }
-            case R.id.star: {
-                playTone(ToneGenerator.TONE_DTMF_S);
-                keyPressed(KeyEvent.KEYCODE_STAR);
-                return;
-            }
-            case R.id.deleteButton: {
-                keyPressed(KeyEvent.KEYCODE_DEL);
-                return;
-            }
-            case R.id.dialButton: {
-                mHaptic.vibrate();  // Vibrate here too, just like we do for the regular keys
-                dialButtonPressed();
-                return;
-            }
-            case R.id.voicemailButton: {
-                callVoicemail();
-                mHaptic.vibrate();
-                return;
-            }
-            case R.id.digits: {
-                if (!isDigitsEmpty()) {
-                    mDigits.setCursorVisible(true);
-                }
-                return;
-            }
-        }
-    }
-
-    public boolean onLongClick(View view) {
-        final Editable digits = mDigits.getText();
-        int id = view.getId();
-        switch (id) {
-            case R.id.deleteButton: {
-                digits.clear();
-                // TODO: The framework forgets to clear the pressed
-                // status of disabled button. Until this is fixed,
-                // clear manually the pressed status. b/2133127
-                mDelete.setPressed(false);
-                return true;
-            }
-            case R.id.one: {
-                if (isDigitsEmpty()) {
-                    callVoicemail();
-                    return true;
-                }
-                return false;
-            }
-            case R.id.zero: {
-                keyPressed(KeyEvent.KEYCODE_PLUS);
-                return true;
-            }
-        }
-        return false;
-    }
-
-    void callVoicemail() {
-        startActivity(newVoicemailIntent());
-        mDigits.getText().clear(); // TODO: Fix bug 1745781
-        finish();
-    }
-
-    /**
-     * In most cases, when the dial button is pressed, there is a
-     * number in digits area. Pack it in the intent, start the
-     * outgoing call broadcast as a separate task and finish this
-     * activity.
-     *
-     * When there is no digit and the phone is CDMA and off hook,
-     * we're sending a blank flash for CDMA. CDMA networks use Flash
-     * messages when special processing needs to be done, mainly for
-     * 3-way or call waiting scenarios. Presumably, here we're in a
-     * special 3-way scenario where the network needs a blank flash
-     * before being able to add the new participant.  (This is not the
-     * case with all 3-way calls, just certain CDMA infrastructures.)
-     *
-     * Otherwise, there is no digit, display the last dialed
-     * number. Don't finish since the user may want to edit it. The
-     * user needs to press the dial button again, to dial it (general
-     * case described above).
-     */
-    void dialButtonPressed() {
-        if (isDigitsEmpty()) { // No number entered.
-            if (phoneIsCdma() && phoneIsOffhook()) {
-                // This is really CDMA specific. On GSM is it possible
-                // to be off hook and wanted to add a 3rd party using
-                // the redial feature.
-                startActivity(newFlashIntent());
-            } else {
-                if (!TextUtils.isEmpty(mLastNumberDialed)) {
-                    mDigits.setText(mLastNumberDialed);
-                } else {
-                    // There's no "last number dialed" or the
-                    // background query is still running. There's
-                    // nothing useful for the Dial button to do in
-                    // this case.  Note: with a soft dial button, this
-                    // can never happens since the dial button is
-                    // disabled under these conditons.
-                    playTone(ToneGenerator.TONE_PROP_NACK);
-                }
-            }
-        } else {
-            final String number = mDigits.getText().toString();
-
-            startActivity(newDialNumberIntent(number));
-            mDigits.getText().clear();  // TODO: Fix bug 1745781
-            finish();
-        }
-    }
-
-
-    /**
-     * Plays the specified tone for TONE_LENGTH_MS milliseconds.
-     *
-     * The tone is played locally, using the audio stream for phone calls.
-     * Tones are played only if the "Audible touch tones" user preference
-     * is checked, and are NOT played if the device is in silent mode.
-     *
-     * @param tone a tone code from {@link ToneGenerator}
-     */
-    void playTone(int tone) {
-        // if local tone playback is disabled, just return.
-        if (!mDTMFToneEnabled) {
-            return;
-        }
-
-        // Also do nothing if the phone is in silent mode.
-        // We need to re-check the ringer mode for *every* playTone()
-        // call, rather than keeping a local flag that's updated in
-        // onResume(), since it's possible to toggle silent mode without
-        // leaving the current activity (via the ENDCALL-longpress menu.)
-        AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-        int ringerMode = audioManager.getRingerMode();
-        if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
-            || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
-            return;
-        }
-
-        synchronized(mToneGeneratorLock) {
-            if (mToneGenerator == null) {
-                Log.w(TAG, "playTone: mToneGenerator == null, tone: "+tone);
-                return;
-            }
-
-            // Start the new tone (will stop any playing tone)
-            mToneGenerator.startTone(tone, TONE_LENGTH_MS);
-        }
-    }
-
-    /**
-     * Brings up the "dialpad chooser" UI in place of the usual Dialer
-     * elements (the textfield/button and the dialpad underneath).
-     *
-     * We show this UI if the user brings up the Dialer while a call is
-     * already in progress, since there's a good chance we got here
-     * accidentally (and the user really wanted the in-call dialpad instead).
-     * So in this situation we display an intermediate UI that lets the user
-     * explicitly choose between the in-call dialpad ("Use touch tone
-     * keypad") and the regular Dialer ("Add call").  (Or, the option "Return
-     * to call in progress" just goes back to the in-call UI with no dialpad
-     * at all.)
-     *
-     * @param enabled If true, show the "dialpad chooser" instead
-     *                of the regular Dialer UI
-     */
-    private void showDialpadChooser(boolean enabled) {
-        if (enabled) {
-            // Log.i(TAG, "Showing dialpad chooser!");
-            mDigits.setVisibility(View.GONE);
-            if (mDialpad != null) mDialpad.setVisibility(View.GONE);
-            mVoicemailDialAndDeleteRow.setVisibility(View.GONE);
-            mDialpadChooser.setVisibility(View.VISIBLE);
-
-            // Instantiate the DialpadChooserAdapter and hook it up to the
-            // ListView.  We do this only once.
-            if (mDialpadChooserAdapter == null) {
-                mDialpadChooserAdapter = new DialpadChooserAdapter(this);
-                mDialpadChooser.setAdapter(mDialpadChooserAdapter);
-            }
-        } else {
-            // Log.i(TAG, "Displaying normal Dialer UI.");
-            mDigits.setVisibility(View.VISIBLE);
-            if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
-            mVoicemailDialAndDeleteRow.setVisibility(View.VISIBLE);
-            mDialpadChooser.setVisibility(View.GONE);
-        }
-    }
-
-    /**
-     * @return true if we're currently showing the "dialpad chooser" UI.
-     */
-    private boolean dialpadChooserVisible() {
-        return mDialpadChooser.getVisibility() == View.VISIBLE;
-    }
-
-    /**
-     * Simple list adapter, binding to an icon + text label
-     * for each item in the "dialpad chooser" list.
-     */
-    private static class DialpadChooserAdapter extends BaseAdapter {
-        private LayoutInflater mInflater;
-
-        // Simple struct for a single "choice" item.
-        static class ChoiceItem {
-            String text;
-            Bitmap icon;
-            int id;
-
-            public ChoiceItem(String s, Bitmap b, int i) {
-                text = s;
-                icon = b;
-                id = i;
-            }
-        }
-
-        // IDs for the possible "choices":
-        static final int DIALPAD_CHOICE_USE_DTMF_DIALPAD = 101;
-        static final int DIALPAD_CHOICE_RETURN_TO_CALL = 102;
-        static final int DIALPAD_CHOICE_ADD_NEW_CALL = 103;
-
-        private static final int NUM_ITEMS = 3;
-        private ChoiceItem mChoiceItems[] = new ChoiceItem[NUM_ITEMS];
-
-        public DialpadChooserAdapter(Context context) {
-            // Cache the LayoutInflate to avoid asking for a new one each time.
-            mInflater = LayoutInflater.from(context);
-
-            // Initialize the possible choices.
-            // TODO: could this be specified entirely in XML?
-
-            // - "Use touch tone keypad"
-            mChoiceItems[0] = new ChoiceItem(
-                    context.getString(R.string.dialer_useDtmfDialpad),
-                    BitmapFactory.decodeResource(context.getResources(),
-                                                 R.drawable.ic_dialer_fork_tt_keypad),
-                    DIALPAD_CHOICE_USE_DTMF_DIALPAD);
-
-            // - "Return to call in progress"
-            mChoiceItems[1] = new ChoiceItem(
-                    context.getString(R.string.dialer_returnToInCallScreen),
-                    BitmapFactory.decodeResource(context.getResources(),
-                                                 R.drawable.ic_dialer_fork_current_call),
-                    DIALPAD_CHOICE_RETURN_TO_CALL);
-
-            // - "Add call"
-            mChoiceItems[2] = new ChoiceItem(
-                    context.getString(R.string.dialer_addAnotherCall),
-                    BitmapFactory.decodeResource(context.getResources(),
-                                                 R.drawable.ic_dialer_fork_add_call),
-                    DIALPAD_CHOICE_ADD_NEW_CALL);
-        }
-
-        public int getCount() {
-            return NUM_ITEMS;
-        }
-
-        /**
-         * Return the ChoiceItem for a given position.
-         */
-        public Object getItem(int position) {
-            return mChoiceItems[position];
-        }
-
-        /**
-         * Return a unique ID for each possible choice.
-         */
-        public long getItemId(int position) {
-            return position;
-        }
-
-        /**
-         * Make a view for each row.
-         */
-        public View getView(int position, View convertView, ViewGroup parent) {
-            // When convertView is non-null, we can reuse it (there's no need
-            // to reinflate it.)
-            if (convertView == null) {
-                convertView = mInflater.inflate(R.layout.dialpad_chooser_list_item, null);
-            }
-
-            TextView text = (TextView) convertView.findViewById(R.id.text);
-            text.setText(mChoiceItems[position].text);
-
-            ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
-            icon.setImageBitmap(mChoiceItems[position].icon);
-
-            return convertView;
-        }
-    }
-
-    /**
-     * Handle clicks from the dialpad chooser.
-     */
-    public void onItemClick(AdapterView parent, View v, int position, long id) {
-        DialpadChooserAdapter.ChoiceItem item =
-                (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
-        int itemId = item.id;
-        switch (itemId) {
-            case DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD:
-                // Log.i(TAG, "DIALPAD_CHOICE_USE_DTMF_DIALPAD");
-                // Fire off an intent to go back to the in-call UI
-                // with the dialpad visible.
-                returnToInCallScreen(true);
-                break;
-
-            case DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL:
-                // Log.i(TAG, "DIALPAD_CHOICE_RETURN_TO_CALL");
-                // Fire off an intent to go back to the in-call UI
-                // (with the dialpad hidden).
-                returnToInCallScreen(false);
-                break;
-
-            case DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL:
-                // Log.i(TAG, "DIALPAD_CHOICE_ADD_NEW_CALL");
-                // Ok, guess the user really did want to be here (in the
-                // regular Dialer) after all.  Bring back the normal Dialer UI.
-                showDialpadChooser(false);
-                break;
-
-            default:
-                Log.w(TAG, "onItemClick: unexpected itemId: " + itemId);
-                break;
-        }
-    }
-
-    /**
-     * Returns to the in-call UI (where there's presumably a call in
-     * progress) in response to the user selecting "use touch tone keypad"
-     * or "return to call" from the dialpad chooser.
-     */
-    private void returnToInCallScreen(boolean showDialpad) {
-        try {
-            ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
-            if (phone != null) phone.showCallScreenWithDialpad(showDialpad);
-        } catch (RemoteException e) {
-            Log.w(TAG, "phone.showCallScreenWithDialpad() failed", e);
-        }
-
-        // Finally, finish() ourselves so that we don't stay on the
-        // activity stack.
-        // Note that we do this whether or not the showCallScreenWithDialpad()
-        // call above had any effect or not!  (That call is a no-op if the
-        // phone is idle, which can happen if the current call ends while
-        // the dialpad chooser is up.  In this case we can't show the
-        // InCallScreen, and there's no point staying here in the Dialer,
-        // so we just take the user back where he came from...)
-        finish();
-    }
-
-    /**
-     * @return true if the phone is "in use", meaning that at least one line
-     *              is active (ie. off hook or ringing or dialing).
-     */
-    private boolean phoneIsInUse() {
-        boolean phoneInUse = false;
-        try {
-            ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
-            if (phone != null) phoneInUse = !phone.isIdle();
-        } catch (RemoteException e) {
-            Log.w(TAG, "phone.isIdle() failed", e);
-        }
-        return phoneInUse;
-    }
-
-    /**
-     * @return true if the phone is a CDMA phone type
-     */
-    private boolean phoneIsCdma() {
-        boolean isCdma = false;
-        try {
-            ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
-            if (phone != null) {
-                isCdma = (phone.getActivePhoneType() == TelephonyManager.PHONE_TYPE_CDMA);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "phone.getActivePhoneType() failed", e);
-        }
-        return isCdma;
-    }
-
-    /**
-     * @return true if the phone state is OFFHOOK
-     */
-    private boolean phoneIsOffhook() {
-        boolean phoneOffhook = false;
-        try {
-            ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
-            if (phone != null) phoneOffhook = phone.isOffhook();
-        } catch (RemoteException e) {
-            Log.w(TAG, "phone.isOffhook() failed", e);
-        }
-        return phoneOffhook;
-    }
-
-
-    /**
-     * Returns true whenever any one of the options from the menu is selected.
-     * Code changes to support dialpad options
-     */
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case MENU_2S_PAUSE:
-                updateDialString(",");
-                return true;
-            case MENU_WAIT:
-                updateDialString(";");
-                return true;
-        }
-        return false;
-    }
-
-    /**
-     * Updates the dial string (mDigits) after inserting a Pause character (,)
-     * or Wait character (;).
-     */
-    private void updateDialString(String newDigits) {
-        int selectionStart;
-        int selectionEnd;
-
-        // SpannableStringBuilder editable_text = new SpannableStringBuilder(mDigits.getText());
-        int anchor = mDigits.getSelectionStart();
-        int point = mDigits.getSelectionEnd();
-
-        selectionStart = Math.min(anchor, point);
-        selectionEnd = Math.max(anchor, point);
-
-        Editable digits = mDigits.getText();
-        if (selectionStart != -1 ) {
-            if (selectionStart == selectionEnd) {
-                // then there is no selection. So insert the pause at this
-                // position and update the mDigits.
-                digits.replace(selectionStart, selectionStart, newDigits);
-            } else {
-                digits.replace(selectionStart, selectionEnd, newDigits);
-                // Unselect: back to a regular cursor, just pass the character inserted.
-                mDigits.setSelection(selectionStart + 1);
-            }
-        } else {
-            int len = mDigits.length();
-            digits.replace(len, len, newDigits);
-        }
-    }
-
-    /**
-     * Update the enabledness of the "Dial" and "Backspace" buttons if applicable.
-     */
-    private void updateDialAndDeleteButtonEnabledState() {
-        final boolean digitsNotEmpty = !isDigitsEmpty();
-
-        if (mDialButton != null) {
-            // On CDMA phones, if we're already on a call, we *always*
-            // enable the Dial button (since you can press it without
-            // entering any digits to send an empty flash.)
-            if (phoneIsCdma() && phoneIsOffhook()) {
-                mDialButton.setEnabled(true);
-            } else {
-                // Common case: GSM, or CDMA but not on a call.
-                // Enable the Dial button if some digits have
-                // been entered, or if there is a last dialed number
-                // that could be redialed.
-                mDialButton.setEnabled(digitsNotEmpty ||
-                                       !TextUtils.isEmpty(mLastNumberDialed));
-            }
-        }
-        mDelete.setEnabled(digitsNotEmpty);
-    }
-
-
-    /**
-     * Check if voicemail is enabled/accessible.
-     */
-    private void initVoicemailButton() {
-        boolean hasVoicemail = false;
-        try {
-            hasVoicemail = TelephonyManager.getDefault().getVoiceMailNumber() != null;
-        } catch (SecurityException se) {
-            // Possibly no READ_PHONE_STATE privilege.
-        }
-
-        mVoicemailButton = mVoicemailDialAndDeleteRow.findViewById(R.id.voicemailButton);
-        if (hasVoicemail) {
-            mVoicemailButton.setOnClickListener(this);
-        } else {
-            mVoicemailButton.setEnabled(false);
-        }
-    }
-
-    /**
-     * This function return true if Wait menu item can be shown
-     * otherwise returns false. Assumes the passed string is non-empty
-     * and the 0th index check is not required.
-     */
-    private boolean showWait(int start, int end, String digits) {
-        if (start == end) {
-            // visible false in this case
-            if (start > digits.length()) return false;
-
-            // preceding char is ';', so visible should be false
-            if (digits.charAt(start-1) == ';') return false;
-
-            // next char is ';', so visible should be false
-            if ((digits.length() > start) && (digits.charAt(start) == ';')) return false;
-        } else {
-            // visible false in this case
-            if (start > digits.length() || end > digits.length()) return false;
-
-            // In this case we need to just check for ';' preceding to start
-            // or next to end
-            if (digits.charAt(start-1) == ';') return false;
-        }
-        return true;
-    }
-
-    /**
-     * @return true if the widget with the phone number digits is empty.
-     */
-    private boolean isDigitsEmpty() {
-        return mDigits.length() == 0;
-    }
-
-    /**
-     * Starts the asyn query to get the last dialed/outgoing
-     * number. When the background query finishes, mLastNumberDialed
-     * is set to the last dialed number or an empty string if none
-     * exists yet.
-     */
-    private void queryLastOutgoingCall() {
-        mLastNumberDialed = EMPTY_NUMBER;
-        CallLogAsync.GetLastOutgoingCallArgs lastCallArgs =
-                new CallLogAsync.GetLastOutgoingCallArgs(
-                    this,
-                    new CallLogAsync.OnLastOutgoingCallComplete() {
-                        public void lastOutgoingCall(String number) {
-                            // TODO: Filter out emergency numbers if
-                            // the carrier does not want redial for
-                            // these.
-                            mLastNumberDialed = number;
-                            updateDialAndDeleteButtonEnabledState();
-                        }
-                    });
-        mCallLog.getLastOutgoingCall(lastCallArgs);
-    }
-
     @Override
     public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
             boolean globalSearch) {
@@ -1260,25 +181,4 @@
             ContactsSearchManager.startSearch(this, initialQuery);
         }
     }
-
-    // Helpers for the call intents.
-    private Intent newVoicemailIntent() {
-        final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
-                                         Uri.fromParts("voicemail", EMPTY_NUMBER, null));
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        return intent;
-    }
-
-    private Intent newFlashIntent() {
-        final Intent intent = newDialNumberIntent(EMPTY_NUMBER);
-        intent.putExtra(EXTRA_SEND_EMPTY_FLASH, true);
-        return intent;
-    }
-
-    private Intent newDialNumberIntent(String number) {
-        final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
-                                         Uri.fromParts("tel", number, null));
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        return intent;
-    }
 }
diff --git a/src/com/android/contacts/activities/DialtactsActivity.java b/src/com/android/contacts/activities/DialtactsActivity.java
index 5054309..869ae2a 100644
--- a/src/com/android/contacts/activities/DialtactsActivity.java
+++ b/src/com/android/contacts/activities/DialtactsActivity.java
@@ -51,7 +51,7 @@
     private static final int TAB_INDEX_CONTACTS = 2;
     private static final int TAB_INDEX_FAVORITES = 3;
 
-    static final String EXTRA_IGNORE_STATE = "ignore-state";
+    public static final String EXTRA_IGNORE_STATE = "ignore-state";
 
     /** Name of the dialtacts shared preferences */
     static final String PREFS_DIALTACTS = "dialtacts";
@@ -91,6 +91,7 @@
         setupCallLogTab();
         setupContactsTab();
         setupFavoritesTab();
+        setupGroupsTab();
 
         // Load the last manually loaded tab
         final SharedPreferences prefs = getSharedPreferences(PREFS_DIALTACTS, MODE_PRIVATE);
@@ -172,6 +173,18 @@
                 .setContent(intent));
     }
 
+    private void setupGroupsTab() {
+        // This is a temporary intent action until the refactoring for the phone/contacts
+        // split is complete.
+        Intent intent = new Intent("com.android.phone.action.GROUPS_LIST");
+                        intent.setClass(this, GroupBrowserActivity.class);
+
+        mTabHost.addTab(mTabHost.newTabSpec("groups")
+                .setIndicator(getString(R.string.contactsGroupsLabel),
+                        getResources().getDrawable(R.drawable.ic_menu_display_all_holo_light))
+                .setContent(intent));
+    }
+
     /**
      * Returns true if the intent is due to hitting the green send key while in a call.
      *
diff --git a/src/com/android/contacts/activities/GroupBrowserActivity.java b/src/com/android/contacts/activities/GroupBrowserActivity.java
new file mode 100644
index 0000000..e8b3ad8
--- /dev/null
+++ b/src/com/android/contacts/activities/GroupBrowserActivity.java
@@ -0,0 +1,55 @@
+/*
+ * 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.activities;
+
+import com.android.contacts.ContactsActivity;
+import com.android.contacts.R;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Displays a list to browse groups.
+ */
+public class GroupBrowserActivity extends ContactsActivity {
+
+    private static final String TAG = "GroupBrowserActivity";
+
+    public GroupBrowserActivity() {
+    }
+
+    @Override
+    protected void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+        configureContentView(true, savedState);
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        setIntent(intent);
+        configureContentView(false, null);
+    }
+
+    private void configureContentView(boolean createContentView, Bundle savedState) {
+        // TODO: Create Intent Resolver to handle the different ways users can get to this list.
+        // TODO: Use savedState if necessary
+        // TODO: Setup action bar
+        if (createContentView) {
+            setContentView(R.layout.group_browser_activity);
+        }
+    }
+}
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
new file mode 100644
index 0000000..fc5fcd9
--- /dev/null
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -0,0 +1,1219 @@
+/*
+ * 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.dialpad;
+
+import com.android.contacts.ContactsUtils;
+import com.android.contacts.R;
+import com.android.contacts.SpecialCharSequenceMgr;
+import com.android.contacts.activities.DialtactsActivity;
+import com.android.internal.telephony.ITelephony;
+import com.android.phone.CallLogAsync;
+import com.android.phone.HapticFeedback;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.ToneGenerator;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.provider.Contacts.Intents.Insert;
+import android.provider.Contacts.People;
+import android.provider.Contacts.Phones;
+import android.provider.Contacts.PhonesColumns;
+import android.provider.Settings;
+import android.telephony.PhoneNumberFormattingTextWatcher;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.text.method.DialerKeyListener;
+import android.util.Log;
+import android.view.KeyEvent;
+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.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * Fragment that displays a twelve-key phone dialpad.
+ */
+public class DialpadFragment extends Fragment
+        implements View.OnClickListener,
+        View.OnLongClickListener, View.OnKeyListener,
+        AdapterView.OnItemClickListener, TextWatcher {
+    private static final String TAG = "DialpadFragment";
+
+    private static final String EMPTY_NUMBER = "";
+
+    /** The length of DTMF tones in milliseconds */
+    private static final int TONE_LENGTH_MS = 150;
+
+    /** The DTMF tone volume relative to other sounds in the stream */
+    private static final int TONE_RELATIVE_VOLUME = 80;
+
+    /** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */
+    private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+    private EditText mDigits;
+    private View mDelete;
+    private MenuItem mAddToContactMenuItem;
+    private ToneGenerator mToneGenerator;
+    private Object mToneGeneratorLock = new Object();
+    private Drawable mDigitsBackground;
+    private Drawable mDigitsEmptyBackground;
+    private View mDialpad;
+    private View mVoicemailDialAndDeleteRow;
+    private View mVoicemailButton;
+    private View mDialButton;
+    private ListView mDialpadChooser;
+    private DialpadChooserAdapter mDialpadChooserAdapter;
+    //Member variables for dialpad options
+    private MenuItem m2SecPauseMenuItem;
+    private MenuItem mWaitMenuItem;
+    private static final int MENU_ADD_CONTACTS = 1;
+    private static final int MENU_2S_PAUSE = 2;
+    private static final int MENU_WAIT = 3;
+
+    // Last number dialed, retrieved asynchronously from the call DB
+    // in onCreate. This number is displayed when the user hits the
+    // send key and cleared in onPause.
+    CallLogAsync mCallLog = new CallLogAsync();
+    private String mLastNumberDialed = EMPTY_NUMBER;
+
+    // determines if we want to playback local DTMF tones.
+    private boolean mDTMFToneEnabled;
+
+    // Vibration (haptic feedback) for dialer key presses.
+    private HapticFeedback mHaptic = new HapticFeedback();
+
+    /** Identifier for the "Add Call" intent extra. */
+    static final String ADD_CALL_MODE_KEY = "add_call_mode";
+
+    /**
+     * Identifier for intent extra for sending an empty Flash message for
+     * CDMA networks. This message is used by the network to simulate a
+     * press/depress of the "hookswitch" of a landline phone. Aka "empty flash".
+     *
+     * TODO: Using an intent extra to tell the phone to send this flash is a
+     * temporary measure. To be replaced with an ITelephony call in the future.
+     * TODO: Keep in sync with the string defined in OutgoingCallBroadcaster.java
+     * in Phone app until this is replaced with the ITelephony API.
+     */
+    static final String EXTRA_SEND_EMPTY_FLASH
+            = "com.android.phone.extra.SEND_EMPTY_FLASH";
+
+    /** Indicates if we are opening this dialer to add a call from the InCallScreen. */
+    private boolean mIsAddCallMode;
+
+    private String mCurrentCountryIso;
+
+    PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+            /**
+             * Listen for phone state changes so that we can take down the
+             * "dialpad chooser" if the phone becomes idle while the
+             * chooser UI is visible.
+             */
+            @Override
+            public void onCallStateChanged(int state, String incomingNumber) {
+                // Log.i(TAG, "PhoneStateListener.onCallStateChanged: "
+                //       + state + ", '" + incomingNumber + "'");
+                if ((state == TelephonyManager.CALL_STATE_IDLE) && dialpadChooserVisible()) {
+                    // Log.i(TAG, "Call ended with dialpad chooser visible!  Taking it down...");
+                    // Note there's a race condition in the UI here: the
+                    // dialpad chooser could conceivably disappear (on its
+                    // own) at the exact moment the user was trying to select
+                    // one of the choices, which would be confusing.  (But at
+                    // least that's better than leaving the dialpad chooser
+                    // onscreen, but useless...)
+                    showDialpadChooser(false);
+                }
+            }
+        };
+
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+        // Do nothing
+    }
+
+    public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
+        // Do nothing
+        // DTMF Tones do not need to be played here any longer -
+        // the DTMF dialer handles that functionality now.
+    }
+
+    public void afterTextChanged(Editable input) {
+        if (SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), mDigits)) {
+            // A special sequence was entered, clear the digits
+            mDigits.getText().clear();
+        }
+
+        if (!isDigitsEmpty()) {
+            mDigits.setBackgroundDrawable(mDigitsBackground);
+        } else {
+            mDigits.setCursorVisible(false);
+            mDigits.setBackgroundDrawable(mDigitsEmptyBackground);
+        }
+
+        updateDialAndDeleteButtonEnabledState();
+    }
+
+    @Override
+    public void onCreate(Bundle state) {
+        super.onCreate(state);
+
+        mCurrentCountryIso = ContactsUtils.getCurrentCountryIso(getActivity());
+
+        try {
+            mHaptic.init(getActivity(),
+                         getResources().getBoolean(R.bool.config_enable_dialer_key_vibration));
+        } catch (Resources.NotFoundException nfe) {
+             Log.e(TAG, "Vibrate control bool missing.", nfe);
+        }
+
+        setHasOptionsMenu(true);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+        View fragmentView = inflater.inflate(R.layout.dialpad_fragment, container, false);
+
+        // Load up the resources for the text field.
+        Resources r = getResources();
+        mDigitsBackground = r.getDrawable(R.drawable.btn_dial_textfield_active);
+        mDigitsEmptyBackground = r.getDrawable(R.drawable.btn_dial_textfield);
+
+        mDigits = (EditText) fragmentView.findViewById(R.id.digits);
+        mDigits.setKeyListener(DialerKeyListener.getInstance());
+        mDigits.setOnClickListener(this);
+        mDigits.setOnKeyListener(this);
+
+        maybeAddNumberFormatting();
+
+        // Check for the presence of the keypad
+        View oneButton = fragmentView.findViewById(R.id.one);
+        if (oneButton != null) {
+            setupKeypad(fragmentView);
+        }
+
+        mVoicemailDialAndDeleteRow = fragmentView.findViewById(R.id.voicemailAndDialAndDelete);
+
+        initVoicemailButton();
+
+        // Check whether we should show the onscreen "Dial" button.
+        mDialButton = mVoicemailDialAndDeleteRow.findViewById(R.id.dialButton);
+
+        if (r.getBoolean(R.bool.config_show_onscreen_dial_button)) {
+            mDialButton.setOnClickListener(this);
+        } else {
+            mDialButton.setVisibility(View.GONE); // It's VISIBLE by default
+            mDialButton = null;
+        }
+
+        mDelete = mVoicemailDialAndDeleteRow.findViewById(R.id.deleteButton);
+        mDelete.setOnClickListener(this);
+        mDelete.setOnLongClickListener(this);
+
+        mDialpad = fragmentView.findViewById(R.id.dialpad);  // This is null in landscape mode.
+
+        // In landscape we put the keyboard in phone mode.
+        // In portrait we prevent the soft keyboard to show since the
+        // dialpad acts as one already.
+        if (null == mDialpad) {
+            mDigits.setInputType(android.text.InputType.TYPE_CLASS_PHONE);
+        } else {
+            mDigits.setInputType(android.text.InputType.TYPE_NULL);
+        }
+
+        // Set up the "dialpad chooser" UI; see showDialpadChooser().
+        mDialpadChooser = (ListView) fragmentView.findViewById(R.id.dialpadChooser);
+        mDialpadChooser.setOnItemClickListener(this);
+
+        return fragmentView;
+    }
+
+    public EditText getDigitsWidget() {
+        return mDigits;
+    }
+
+    private void maybeAddNumberFormatting() {
+        mDigits.addTextChangedListener(new PhoneNumberFormattingTextWatcher(mCurrentCountryIso));
+    }
+
+    /**
+     * Handles the intent that launched us.
+     *
+     * We can be launched either with ACTION_DIAL or ACTION_VIEW (which
+     * may include a phone number to pre-load), or ACTION_MAIN (which just
+     * brings up a blank dialpad).
+     *
+     * @return true IFF the current intent has the DialtactsActivity.EXTRA_IGNORE_STATE
+     *    extra set to true, which indicates (to our container) that we should ignore
+     *    any possible saved state, and instead reset our state based on the parent's
+     *    intent.
+     */
+    public boolean resolveIntent() {
+        boolean ignoreState = false;
+
+        // Find the proper intent
+        final Intent intent;
+        if (getActivity().isChild()) {
+            intent = getActivity().getParent().getIntent();
+            ignoreState = intent.getBooleanExtra(DialtactsActivity.EXTRA_IGNORE_STATE, false);
+        } else {
+            intent = getActivity().getIntent();
+        }
+        // Log.i(TAG, "==> resolveIntent(): intent: " + intent);
+
+        // by default we are not adding a call.
+        mIsAddCallMode = false;
+
+        // By default we don't show the "dialpad chooser" UI.
+        boolean needToShowDialpadChooser = false;
+
+        // Resolve the intent
+        final String action = intent.getAction();
+        if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
+            // see if we are "adding a call" from the InCallScreen; false by default.
+            mIsAddCallMode = intent.getBooleanExtra(ADD_CALL_MODE_KEY, false);
+
+            Uri uri = intent.getData();
+            if (uri != null) {
+                if ("tel".equals(uri.getScheme())) {
+                    // Put the requested number into the input area
+                    String data = uri.getSchemeSpecificPart();
+                    setFormattedDigits(data, null);
+                } else {
+                    String type = intent.getType();
+                    if (People.CONTENT_ITEM_TYPE.equals(type)
+                            || Phones.CONTENT_ITEM_TYPE.equals(type)) {
+                        // Query the phone number
+                        Cursor c = getActivity().getContentResolver().query(intent.getData(),
+                                new String[] {PhonesColumns.NUMBER, PhonesColumns.NUMBER_KEY},
+                                null, null, null);
+                        if (c != null) {
+                            if (c.moveToFirst()) {
+                                // Put the number into the input area
+                                setFormattedDigits(c.getString(0), c.getString(1));
+                            }
+                            c.close();
+                        }
+                    }
+                }
+            } else {
+                // ACTION_DIAL or ACTION_VIEW with no data.
+                // This behaves basically like ACTION_MAIN: If there's
+                // already an active call, bring up an intermediate UI to
+                // make the user confirm what they really want to do.
+                // Be sure *not* to show the dialpad chooser if this is an
+                // explicit "Add call" action, though.
+                if (!mIsAddCallMode && phoneIsInUse()) {
+                    needToShowDialpadChooser = true;
+                }
+            }
+        } else if (Intent.ACTION_MAIN.equals(action)) {
+            // The MAIN action means we're bringing up a blank dialer
+            // (e.g. by selecting the Home shortcut, or tabbing over from
+            // Contacts or Call log.)
+            //
+            // At this point, IF there's already an active call, there's a
+            // good chance that the user got here accidentally (but really
+            // wanted the in-call dialpad instead).  So we bring up an
+            // intermediate UI to make the user confirm what they really
+            // want to do.
+            if (phoneIsInUse()) {
+                // Log.i(TAG, "resolveIntent(): phone is in use; showing dialpad chooser!");
+                needToShowDialpadChooser = true;
+            }
+        }
+
+        // Bring up the "dialpad chooser" IFF we need to make the user
+        // confirm which dialpad they really want.
+        showDialpadChooser(needToShowDialpadChooser);
+
+        return ignoreState;
+    }
+
+    private void setFormattedDigits(String data, String normalizedNumber) {
+        // strip the non-dialable numbers out of the data string.
+        String dialString = PhoneNumberUtils.extractNetworkPortion(data);
+        dialString =
+                PhoneNumberUtils.formatNumber(dialString, normalizedNumber, mCurrentCountryIso);
+        if (!TextUtils.isEmpty(dialString)) {
+            Editable digits = mDigits.getText();
+            digits.replace(0, digits.length(), dialString);
+            // for some reason this isn't getting called in the digits.replace call above..
+            // but in any case, this will make sure the background drawable looks right
+            afterTextChanged(digits);
+        }
+    }
+
+    private void setupKeypad(View fragmentView) {
+        // Setup the listeners for the buttons
+        View view = fragmentView.findViewById(R.id.one);
+        view.setOnClickListener(this);
+        view.setOnLongClickListener(this);
+
+        fragmentView.findViewById(R.id.two).setOnClickListener(this);
+        fragmentView.findViewById(R.id.three).setOnClickListener(this);
+        fragmentView.findViewById(R.id.four).setOnClickListener(this);
+        fragmentView.findViewById(R.id.five).setOnClickListener(this);
+        fragmentView.findViewById(R.id.six).setOnClickListener(this);
+        fragmentView.findViewById(R.id.seven).setOnClickListener(this);
+        fragmentView.findViewById(R.id.eight).setOnClickListener(this);
+        fragmentView.findViewById(R.id.nine).setOnClickListener(this);
+        fragmentView.findViewById(R.id.star).setOnClickListener(this);
+
+        view = fragmentView.findViewById(R.id.zero);
+        view.setOnClickListener(this);
+        view.setOnLongClickListener(this);
+
+        fragmentView.findViewById(R.id.pound).setOnClickListener(this);
+    }
+
+    // Do some stuff that needs to happen only once, but which we
+    // can't do directly from onCreate().
+    public void onPostCreate() {
+        // This can't be done in onCreate(), since the auto-restoring of the digits
+        // will play DTMF tones for all the old digits if it is when onRestoreSavedInstanceState()
+        // is called. This method will be called every time the activity is created, and
+        // will always happen after onRestoreSavedInstanceState().
+        mDigits.addTextChangedListener(this);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        // Query the last dialed number. Do it first because hitting
+        // the DB is 'slow'. This call is asynchronous.
+        queryLastOutgoingCall();
+
+        // retrieve the DTMF tone play back setting.
+        mDTMFToneEnabled = Settings.System.getInt(getActivity().getContentResolver(),
+                Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
+
+        // Retrieve the haptic feedback setting.
+        mHaptic.checkSystemSetting();
+
+        // if the mToneGenerator creation fails, just continue without it.  It is
+        // a local audio signal, and is not as important as the dtmf tone itself.
+        synchronized (mToneGeneratorLock) {
+            if (mToneGenerator == null) {
+                try {
+                    // we want the user to be able to control the volume of the dial tones
+                    // outside of a call, so we use the stream type that is also mapped to the
+                    // volume control keys for this activity
+                    mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME);
+                    getActivity().setVolumeControlStream(DIAL_TONE_STREAM_TYPE);
+                } catch (RuntimeException e) {
+                    Log.w(TAG, "Exception caught while creating local tone generator: " + e);
+                    mToneGenerator = null;
+                }
+            }
+        }
+
+        Activity parent = getActivity().getParent();
+        // See if we were invoked with a DIAL intent. If we were, fill in the appropriate
+        // digits in the dialer field.
+        if (parent != null && parent instanceof DialtactsActivity) {
+            Uri dialUri = ((DialtactsActivity) parent).getAndClearDialUri();
+            if (dialUri != null) {
+                resolveIntent();
+            }
+        }
+
+        // While we're in the foreground, listen for phone state changes,
+        // purely so that we can take down the "dialpad chooser" if the
+        // phone becomes idle while the chooser UI is visible.
+        TelephonyManager telephonyManager =
+                (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
+        telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+
+        // Potentially show hint text in the mDigits field when the user
+        // hasn't typed any digits yet.  (If there's already an active call,
+        // this hint text will remind the user that he's about to add a new
+        // call.)
+        //
+        // TODO: consider adding better UI for the case where *both* lines
+        // are currently in use.  (Right now we let the user try to add
+        // another call, but that call is guaranteed to fail.  Perhaps the
+        // entire dialer UI should be disabled instead.)
+        if (phoneIsInUse()) {
+            mDigits.setHint(R.string.dialerDialpadHintText);
+        } else {
+            // Common case; no hint necessary.
+            mDigits.setHint(null);
+
+            // Also, a sanity-check: the "dialpad chooser" UI should NEVER
+            // be visible if the phone is idle!
+            showDialpadChooser(false);
+        }
+
+        updateDialAndDeleteButtonEnabledState();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        // Stop listening for phone state changes.
+        TelephonyManager telephonyManager =
+                (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
+        telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+
+        synchronized (mToneGeneratorLock) {
+            if (mToneGenerator != null) {
+                mToneGenerator.release();
+                mToneGenerator = null;
+            }
+        }
+        // TODO: I wonder if we should not check if the AsyncTask that
+        // lookup the last dialed number has completed.
+        mLastNumberDialed = EMPTY_NUMBER;  // Since we are going to query again, free stale number.
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+
+        mAddToContactMenuItem = menu.add(0, MENU_ADD_CONTACTS, 0, R.string.recentCalls_addToContact)
+                .setIcon(android.R.drawable.ic_menu_add);
+        m2SecPauseMenuItem = menu.add(0, MENU_2S_PAUSE, 0, R.string.add_2sec_pause)
+                .setIcon(R.drawable.ic_menu_2sec_pause);
+        mWaitMenuItem = menu.add(0, MENU_WAIT, 0, R.string.add_wait)
+                .setIcon(R.drawable.ic_menu_wait);
+    }
+
+    /** @return true if an options menu should be shown in our containing activity. */
+    public boolean allowOptionsMenu() {
+        // We never show a menu if the "choose dialpad" UI is up.
+        // Otherwise the menu is allowed (see onPrepareOptionsMenu() below.)
+        return (!dialpadChooserVisible());
+    }
+
+    @Override
+    public void onPrepareOptionsMenu(Menu menu) {
+        // Note that we won't show a menu at all if the "choose dialpad" UI is up.
+        // (See allowOptionsMenu() above, along with DialogActivity.onPrepareOptionsMenu().)
+
+        if (isDigitsEmpty()) {
+            mAddToContactMenuItem.setVisible(false);
+            m2SecPauseMenuItem.setVisible(false);
+            mWaitMenuItem.setVisible(false);
+        } else {
+            CharSequence digits = mDigits.getText();
+
+            // Put the current digits string into an intent
+            Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+            intent.putExtra(Insert.PHONE, digits);
+            intent.setType(People.CONTENT_ITEM_TYPE);
+            mAddToContactMenuItem.setIntent(intent);
+            mAddToContactMenuItem.setVisible(true);
+
+            // Check out whether to show Pause & Wait option menu items
+            int selectionStart;
+            int selectionEnd;
+            String strDigits = digits.toString();
+
+            selectionStart = mDigits.getSelectionStart();
+            selectionEnd = mDigits.getSelectionEnd();
+
+            if (selectionStart != -1) {
+                if (selectionStart > selectionEnd) {
+                    // swap it as we want start to be less then end
+                    int tmp = selectionStart;
+                    selectionStart = selectionEnd;
+                    selectionEnd = tmp;
+                }
+
+                if (selectionStart != 0) {
+                    // Pause can be visible if cursor is not in the begining
+                    m2SecPauseMenuItem.setVisible(true);
+
+                    // For Wait to be visible set of condition to meet
+                    mWaitMenuItem.setVisible(showWait(selectionStart,
+                                                      selectionEnd, strDigits));
+                } else {
+                    // cursor in the beginning both pause and wait to be invisible
+                    m2SecPauseMenuItem.setVisible(false);
+                    mWaitMenuItem.setVisible(false);
+                }
+            } else {
+                // cursor is not selected so assume new digit is added to the end
+                int strLength = strDigits.length();
+                mWaitMenuItem.setVisible(showWait(strLength,
+                                                      strLength, strDigits));
+            }
+        }
+    }
+
+    private void keyPressed(int keyCode) {
+        mHaptic.vibrate();
+        KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
+        mDigits.onKeyDown(keyCode, event);
+    }
+
+    public boolean onKey(View view, int keyCode, KeyEvent event) {
+        switch (view.getId()) {
+            case R.id.digits:
+                if (keyCode == KeyEvent.KEYCODE_ENTER) {
+                    dialButtonPressed();
+                    return true;
+                }
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    public void onClick(View view) {
+        switch (view.getId()) {
+            case R.id.one: {
+                playTone(ToneGenerator.TONE_DTMF_1);
+                keyPressed(KeyEvent.KEYCODE_1);
+                return;
+            }
+            case R.id.two: {
+                playTone(ToneGenerator.TONE_DTMF_2);
+                keyPressed(KeyEvent.KEYCODE_2);
+                return;
+            }
+            case R.id.three: {
+                playTone(ToneGenerator.TONE_DTMF_3);
+                keyPressed(KeyEvent.KEYCODE_3);
+                return;
+            }
+            case R.id.four: {
+                playTone(ToneGenerator.TONE_DTMF_4);
+                keyPressed(KeyEvent.KEYCODE_4);
+                return;
+            }
+            case R.id.five: {
+                playTone(ToneGenerator.TONE_DTMF_5);
+                keyPressed(KeyEvent.KEYCODE_5);
+                return;
+            }
+            case R.id.six: {
+                playTone(ToneGenerator.TONE_DTMF_6);
+                keyPressed(KeyEvent.KEYCODE_6);
+                return;
+            }
+            case R.id.seven: {
+                playTone(ToneGenerator.TONE_DTMF_7);
+                keyPressed(KeyEvent.KEYCODE_7);
+                return;
+            }
+            case R.id.eight: {
+                playTone(ToneGenerator.TONE_DTMF_8);
+                keyPressed(KeyEvent.KEYCODE_8);
+                return;
+            }
+            case R.id.nine: {
+                playTone(ToneGenerator.TONE_DTMF_9);
+                keyPressed(KeyEvent.KEYCODE_9);
+                return;
+            }
+            case R.id.zero: {
+                playTone(ToneGenerator.TONE_DTMF_0);
+                keyPressed(KeyEvent.KEYCODE_0);
+                return;
+            }
+            case R.id.pound: {
+                playTone(ToneGenerator.TONE_DTMF_P);
+                keyPressed(KeyEvent.KEYCODE_POUND);
+                return;
+            }
+            case R.id.star: {
+                playTone(ToneGenerator.TONE_DTMF_S);
+                keyPressed(KeyEvent.KEYCODE_STAR);
+                return;
+            }
+            case R.id.deleteButton: {
+                keyPressed(KeyEvent.KEYCODE_DEL);
+                return;
+            }
+            case R.id.dialButton: {
+                mHaptic.vibrate();  // Vibrate here too, just like we do for the regular keys
+                dialButtonPressed();
+                return;
+            }
+            case R.id.voicemailButton: {
+                callVoicemail();
+                mHaptic.vibrate();
+                return;
+            }
+            case R.id.digits: {
+                if (!isDigitsEmpty()) {
+                    mDigits.setCursorVisible(true);
+                }
+                return;
+            }
+        }
+    }
+
+    public boolean onLongClick(View view) {
+        final Editable digits = mDigits.getText();
+        int id = view.getId();
+        switch (id) {
+            case R.id.deleteButton: {
+                digits.clear();
+                // TODO: The framework forgets to clear the pressed
+                // status of disabled button. Until this is fixed,
+                // clear manually the pressed status. b/2133127
+                mDelete.setPressed(false);
+                return true;
+            }
+            case R.id.one: {
+                if (isDigitsEmpty()) {
+                    callVoicemail();
+                    return true;
+                }
+                return false;
+            }
+            case R.id.zero: {
+                keyPressed(KeyEvent.KEYCODE_PLUS);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void callVoicemail() {
+        startActivity(newVoicemailIntent());
+        mDigits.getText().clear(); // TODO: Fix bug 1745781
+        getActivity().finish();
+    }
+
+    /**
+     * In most cases, when the dial button is pressed, there is a
+     * number in digits area. Pack it in the intent, start the
+     * outgoing call broadcast as a separate task and finish this
+     * activity.
+     *
+     * When there is no digit and the phone is CDMA and off hook,
+     * we're sending a blank flash for CDMA. CDMA networks use Flash
+     * messages when special processing needs to be done, mainly for
+     * 3-way or call waiting scenarios. Presumably, here we're in a
+     * special 3-way scenario where the network needs a blank flash
+     * before being able to add the new participant.  (This is not the
+     * case with all 3-way calls, just certain CDMA infrastructures.)
+     *
+     * Otherwise, there is no digit, display the last dialed
+     * number. Don't finish since the user may want to edit it. The
+     * user needs to press the dial button again, to dial it (general
+     * case described above).
+     */
+    public void dialButtonPressed() {
+        if (isDigitsEmpty()) { // No number entered.
+            if (phoneIsCdma() && phoneIsOffhook()) {
+                // This is really CDMA specific. On GSM is it possible
+                // to be off hook and wanted to add a 3rd party using
+                // the redial feature.
+                startActivity(newFlashIntent());
+            } else {
+                if (!TextUtils.isEmpty(mLastNumberDialed)) {
+                    mDigits.setText(mLastNumberDialed);
+                } else {
+                    // There's no "last number dialed" or the
+                    // background query is still running. There's
+                    // nothing useful for the Dial button to do in
+                    // this case.  Note: with a soft dial button, this
+                    // can never happens since the dial button is
+                    // disabled under these conditons.
+                    playTone(ToneGenerator.TONE_PROP_NACK);
+                }
+            }
+        } else {
+            final String number = mDigits.getText().toString();
+
+            startActivity(newDialNumberIntent(number));
+            mDigits.getText().clear();  // TODO: Fix bug 1745781
+            getActivity().finish();
+        }
+    }
+
+    /**
+     * Plays the specified tone for TONE_LENGTH_MS milliseconds.
+     *
+     * The tone is played locally, using the audio stream for phone calls.
+     * Tones are played only if the "Audible touch tones" user preference
+     * is checked, and are NOT played if the device is in silent mode.
+     *
+     * @param tone a tone code from {@link ToneGenerator}
+     */
+    void playTone(int tone) {
+        // if local tone playback is disabled, just return.
+        if (!mDTMFToneEnabled) {
+            return;
+        }
+
+        // Also do nothing if the phone is in silent mode.
+        // We need to re-check the ringer mode for *every* playTone()
+        // call, rather than keeping a local flag that's updated in
+        // onResume(), since it's possible to toggle silent mode without
+        // leaving the current activity (via the ENDCALL-longpress menu.)
+        AudioManager audioManager =
+                (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
+        int ringerMode = audioManager.getRingerMode();
+        if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
+            || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
+            return;
+        }
+
+        synchronized (mToneGeneratorLock) {
+            if (mToneGenerator == null) {
+                Log.w(TAG, "playTone: mToneGenerator == null, tone: " + tone);
+                return;
+            }
+
+            // Start the new tone (will stop any playing tone)
+            mToneGenerator.startTone(tone, TONE_LENGTH_MS);
+        }
+    }
+
+    /**
+     * Brings up the "dialpad chooser" UI in place of the usual Dialer
+     * elements (the textfield/button and the dialpad underneath).
+     *
+     * We show this UI if the user brings up the Dialer while a call is
+     * already in progress, since there's a good chance we got here
+     * accidentally (and the user really wanted the in-call dialpad instead).
+     * So in this situation we display an intermediate UI that lets the user
+     * explicitly choose between the in-call dialpad ("Use touch tone
+     * keypad") and the regular Dialer ("Add call").  (Or, the option "Return
+     * to call in progress" just goes back to the in-call UI with no dialpad
+     * at all.)
+     *
+     * @param enabled If true, show the "dialpad chooser" instead
+     *                of the regular Dialer UI
+     */
+    private void showDialpadChooser(boolean enabled) {
+        if (enabled) {
+            // Log.i(TAG, "Showing dialpad chooser!");
+            mDigits.setVisibility(View.GONE);
+            if (mDialpad != null) mDialpad.setVisibility(View.GONE);
+            mVoicemailDialAndDeleteRow.setVisibility(View.GONE);
+            mDialpadChooser.setVisibility(View.VISIBLE);
+
+            // Instantiate the DialpadChooserAdapter and hook it up to the
+            // ListView.  We do this only once.
+            if (mDialpadChooserAdapter == null) {
+                mDialpadChooserAdapter = new DialpadChooserAdapter(getActivity());
+                mDialpadChooser.setAdapter(mDialpadChooserAdapter);
+            }
+        } else {
+            // Log.i(TAG, "Displaying normal Dialer UI.");
+            mDigits.setVisibility(View.VISIBLE);
+            if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
+            mVoicemailDialAndDeleteRow.setVisibility(View.VISIBLE);
+            mDialpadChooser.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * @return true if we're currently showing the "dialpad chooser" UI.
+     */
+    private boolean dialpadChooserVisible() {
+        return mDialpadChooser.getVisibility() == View.VISIBLE;
+    }
+
+    /**
+     * Simple list adapter, binding to an icon + text label
+     * for each item in the "dialpad chooser" list.
+     */
+    private static class DialpadChooserAdapter extends BaseAdapter {
+        private LayoutInflater mInflater;
+
+        // Simple struct for a single "choice" item.
+        static class ChoiceItem {
+            String text;
+            Bitmap icon;
+            int id;
+
+            public ChoiceItem(String s, Bitmap b, int i) {
+                text = s;
+                icon = b;
+                id = i;
+            }
+        }
+
+        // IDs for the possible "choices":
+        static final int DIALPAD_CHOICE_USE_DTMF_DIALPAD = 101;
+        static final int DIALPAD_CHOICE_RETURN_TO_CALL = 102;
+        static final int DIALPAD_CHOICE_ADD_NEW_CALL = 103;
+
+        private static final int NUM_ITEMS = 3;
+        private ChoiceItem mChoiceItems[] = new ChoiceItem[NUM_ITEMS];
+
+        public DialpadChooserAdapter(Context context) {
+            // Cache the LayoutInflate to avoid asking for a new one each time.
+            mInflater = LayoutInflater.from(context);
+
+            // Initialize the possible choices.
+            // TODO: could this be specified entirely in XML?
+
+            // - "Use touch tone keypad"
+            mChoiceItems[0] = new ChoiceItem(
+                    context.getString(R.string.dialer_useDtmfDialpad),
+                    BitmapFactory.decodeResource(context.getResources(),
+                                                 R.drawable.ic_dialer_fork_tt_keypad),
+                    DIALPAD_CHOICE_USE_DTMF_DIALPAD);
+
+            // - "Return to call in progress"
+            mChoiceItems[1] = new ChoiceItem(
+                    context.getString(R.string.dialer_returnToInCallScreen),
+                    BitmapFactory.decodeResource(context.getResources(),
+                                                 R.drawable.ic_dialer_fork_current_call),
+                    DIALPAD_CHOICE_RETURN_TO_CALL);
+
+            // - "Add call"
+            mChoiceItems[2] = new ChoiceItem(
+                    context.getString(R.string.dialer_addAnotherCall),
+                    BitmapFactory.decodeResource(context.getResources(),
+                                                 R.drawable.ic_dialer_fork_add_call),
+                    DIALPAD_CHOICE_ADD_NEW_CALL);
+        }
+
+        public int getCount() {
+            return NUM_ITEMS;
+        }
+
+        /**
+         * Return the ChoiceItem for a given position.
+         */
+        public Object getItem(int position) {
+            return mChoiceItems[position];
+        }
+
+        /**
+         * Return a unique ID for each possible choice.
+         */
+        public long getItemId(int position) {
+            return position;
+        }
+
+        /**
+         * Make a view for each row.
+         */
+        public View getView(int position, View convertView, ViewGroup parent) {
+            // When convertView is non-null, we can reuse it (there's no need
+            // to reinflate it.)
+            if (convertView == null) {
+                convertView = mInflater.inflate(R.layout.dialpad_chooser_list_item, null);
+            }
+
+            TextView text = (TextView) convertView.findViewById(R.id.text);
+            text.setText(mChoiceItems[position].text);
+
+            ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
+            icon.setImageBitmap(mChoiceItems[position].icon);
+
+            return convertView;
+        }
+    }
+
+    /**
+     * Handle clicks from the dialpad chooser.
+     */
+    public void onItemClick(AdapterView parent, View v, int position, long id) {
+        DialpadChooserAdapter.ChoiceItem item =
+                (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
+        int itemId = item.id;
+        switch (itemId) {
+            case DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD:
+                // Log.i(TAG, "DIALPAD_CHOICE_USE_DTMF_DIALPAD");
+                // Fire off an intent to go back to the in-call UI
+                // with the dialpad visible.
+                returnToInCallScreen(true);
+                break;
+
+            case DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL:
+                // Log.i(TAG, "DIALPAD_CHOICE_RETURN_TO_CALL");
+                // Fire off an intent to go back to the in-call UI
+                // (with the dialpad hidden).
+                returnToInCallScreen(false);
+                break;
+
+            case DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL:
+                // Log.i(TAG, "DIALPAD_CHOICE_ADD_NEW_CALL");
+                // Ok, guess the user really did want to be here (in the
+                // regular Dialer) after all.  Bring back the normal Dialer UI.
+                showDialpadChooser(false);
+                break;
+
+            default:
+                Log.w(TAG, "onItemClick: unexpected itemId: " + itemId);
+                break;
+        }
+    }
+
+    /**
+     * Returns to the in-call UI (where there's presumably a call in
+     * progress) in response to the user selecting "use touch tone keypad"
+     * or "return to call" from the dialpad chooser.
+     */
+    private void returnToInCallScreen(boolean showDialpad) {
+        try {
+            ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+            if (phone != null) phone.showCallScreenWithDialpad(showDialpad);
+        } catch (RemoteException e) {
+            Log.w(TAG, "phone.showCallScreenWithDialpad() failed", e);
+        }
+
+        // Finally, finish() ourselves so that we don't stay on the
+        // activity stack.
+        // Note that we do this whether or not the showCallScreenWithDialpad()
+        // call above had any effect or not!  (That call is a no-op if the
+        // phone is idle, which can happen if the current call ends while
+        // the dialpad chooser is up.  In this case we can't show the
+        // InCallScreen, and there's no point staying here in the Dialer,
+        // so we just take the user back where he came from...)
+        getActivity().finish();
+    }
+
+    /**
+     * @return true if the phone is "in use", meaning that at least one line
+     *              is active (ie. off hook or ringing or dialing).
+     */
+    private boolean phoneIsInUse() {
+        boolean phoneInUse = false;
+        try {
+            ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+            if (phone != null) phoneInUse = !phone.isIdle();
+        } catch (RemoteException e) {
+            Log.w(TAG, "phone.isIdle() failed", e);
+        }
+        return phoneInUse;
+    }
+
+    /**
+     * @return true if the phone is a CDMA phone type
+     */
+    private boolean phoneIsCdma() {
+        boolean isCdma = false;
+        try {
+            ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+            if (phone != null) {
+                isCdma = (phone.getActivePhoneType() == TelephonyManager.PHONE_TYPE_CDMA);
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "phone.getActivePhoneType() failed", e);
+        }
+        return isCdma;
+    }
+
+    /**
+     * @return true if the phone state is OFFHOOK
+     */
+    private boolean phoneIsOffhook() {
+        boolean phoneOffhook = false;
+        try {
+            ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+            if (phone != null) phoneOffhook = phone.isOffhook();
+        } catch (RemoteException e) {
+            Log.w(TAG, "phone.isOffhook() failed", e);
+        }
+        return phoneOffhook;
+    }
+
+    /**
+     * Returns true whenever any one of the options from the menu is selected.
+     * Code changes to support dialpad options
+     */
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case MENU_2S_PAUSE:
+                updateDialString(",");
+                return true;
+            case MENU_WAIT:
+                updateDialString(";");
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Updates the dial string (mDigits) after inserting a Pause character (,)
+     * or Wait character (;).
+     */
+    private void updateDialString(String newDigits) {
+        int selectionStart;
+        int selectionEnd;
+
+        // SpannableStringBuilder editable_text = new SpannableStringBuilder(mDigits.getText());
+        int anchor = mDigits.getSelectionStart();
+        int point = mDigits.getSelectionEnd();
+
+        selectionStart = Math.min(anchor, point);
+        selectionEnd = Math.max(anchor, point);
+
+        Editable digits = mDigits.getText();
+        if (selectionStart != -1) {
+            if (selectionStart == selectionEnd) {
+                // then there is no selection. So insert the pause at this
+                // position and update the mDigits.
+                digits.replace(selectionStart, selectionStart, newDigits);
+            } else {
+                digits.replace(selectionStart, selectionEnd, newDigits);
+                // Unselect: back to a regular cursor, just pass the character inserted.
+                mDigits.setSelection(selectionStart + 1);
+            }
+        } else {
+            int len = mDigits.length();
+            digits.replace(len, len, newDigits);
+        }
+    }
+
+    /**
+     * Update the enabledness of the "Dial" and "Backspace" buttons if applicable.
+     */
+    private void updateDialAndDeleteButtonEnabledState() {
+        final boolean digitsNotEmpty = !isDigitsEmpty();
+
+        if (mDialButton != null) {
+            // On CDMA phones, if we're already on a call, we *always*
+            // enable the Dial button (since you can press it without
+            // entering any digits to send an empty flash.)
+            if (phoneIsCdma() && phoneIsOffhook()) {
+                mDialButton.setEnabled(true);
+            } else {
+                // Common case: GSM, or CDMA but not on a call.
+                // Enable the Dial button if some digits have
+                // been entered, or if there is a last dialed number
+                // that could be redialed.
+                mDialButton.setEnabled(digitsNotEmpty ||
+                                       !TextUtils.isEmpty(mLastNumberDialed));
+            }
+        }
+        mDelete.setEnabled(digitsNotEmpty);
+    }
+
+    /**
+     * Check if voicemail is enabled/accessible.
+     */
+    private void initVoicemailButton() {
+        boolean hasVoicemail = false;
+        try {
+            hasVoicemail = TelephonyManager.getDefault().getVoiceMailNumber() != null;
+        } catch (SecurityException se) {
+            // Possibly no READ_PHONE_STATE privilege.
+        }
+
+        mVoicemailButton = mVoicemailDialAndDeleteRow.findViewById(R.id.voicemailButton);
+        if (hasVoicemail) {
+            mVoicemailButton.setOnClickListener(this);
+        } else {
+            mVoicemailButton.setEnabled(false);
+        }
+    }
+
+    /**
+     * This function return true if Wait menu item can be shown
+     * otherwise returns false. Assumes the passed string is non-empty
+     * and the 0th index check is not required.
+     */
+    private boolean showWait(int start, int end, String digits) {
+        if (start == end) {
+            // visible false in this case
+            if (start > digits.length()) return false;
+
+            // preceding char is ';', so visible should be false
+            if (digits.charAt(start - 1) == ';') return false;
+
+            // next char is ';', so visible should be false
+            if ((digits.length() > start) && (digits.charAt(start) == ';')) return false;
+        } else {
+            // visible false in this case
+            if (start > digits.length() || end > digits.length()) return false;
+
+            // In this case we need to just check for ';' preceding to start
+            // or next to end
+            if (digits.charAt(start - 1) == ';') return false;
+        }
+        return true;
+    }
+
+    /**
+     * @return true if the widget with the phone number digits is empty.
+     */
+    private boolean isDigitsEmpty() {
+        return mDigits.length() == 0;
+    }
+
+    /**
+     * Starts the asyn query to get the last dialed/outgoing
+     * number. When the background query finishes, mLastNumberDialed
+     * is set to the last dialed number or an empty string if none
+     * exists yet.
+     */
+    private void queryLastOutgoingCall() {
+        mLastNumberDialed = EMPTY_NUMBER;
+        CallLogAsync.GetLastOutgoingCallArgs lastCallArgs =
+                new CallLogAsync.GetLastOutgoingCallArgs(
+                    getActivity(),
+                    new CallLogAsync.OnLastOutgoingCallComplete() {
+                        public void lastOutgoingCall(String number) {
+                            // TODO: Filter out emergency numbers if
+                            // the carrier does not want redial for
+                            // these.
+                            mLastNumberDialed = number;
+                            updateDialAndDeleteButtonEnabledState();
+                        }
+                    });
+        mCallLog.getLastOutgoingCall(lastCallArgs);
+    }
+
+    // Helpers for the call intents.
+    private Intent newVoicemailIntent() {
+        final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
+                                         Uri.fromParts("voicemail", EMPTY_NUMBER, null));
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        return intent;
+    }
+
+    private Intent newFlashIntent() {
+        final Intent intent = newDialNumberIntent(EMPTY_NUMBER);
+        intent.putExtra(EXTRA_SEND_EMPTY_FLASH, true);
+        return intent;
+    }
+
+    private Intent newDialNumberIntent(String number) {
+        final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
+                                         Uri.fromParts("tel", number, null));
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        return intent;
+    }
+}
diff --git a/src/com/android/contacts/editor/RawContactEditorView.java b/src/com/android/contacts/editor/RawContactEditorView.java
index 4effb5e..5ef2c21 100644
--- a/src/com/android/contacts/editor/RawContactEditorView.java
+++ b/src/com/android/contacts/editor/RawContactEditorView.java
@@ -20,11 +20,13 @@
 import com.android.contacts.R;
 import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountType.EditType;
+import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.model.DataKind;
 import com.android.contacts.model.EntityDelta;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
 import com.android.contacts.model.EntityModifier;
 
+import android.accounts.Account;
 import android.content.Context;
 import android.content.Entity;
 import android.database.Cursor;
@@ -68,6 +70,7 @@
 
     private ViewGroup mFields;
 
+    private View mAccountContainer;
     private ImageView mAccountIcon;
     private TextView mAccountTypeTextView;
     private TextView mAccountNameTextView;
@@ -136,6 +139,7 @@
 
         mFields = (ViewGroup)findViewById(R.id.sect_fields);
 
+        mAccountContainer = findViewById(R.id.account);
         mAccountIcon = (ImageView) findViewById(R.id.account_icon);
         mAccountTypeTextView = (TextView) findViewById(R.id.account_type);
         mAccountNameTextView = (TextView) findViewById(R.id.account_name);
@@ -169,22 +173,29 @@
         // Make sure we have StructuredName
         EntityModifier.ensureKindExists(state, type, StructuredName.CONTENT_ITEM_TYPE);
 
-        // Fill in the header info
         ValuesDelta values = state.getValues();
-        String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
-        CharSequence accountType = type.getDisplayLabel(mContext);
-        if (TextUtils.isEmpty(accountType)) {
-            accountType = mContext.getString(R.string.account_phone);
-        }
-        if (!TextUtils.isEmpty(accountName)) {
-            mAccountNameTextView.setText(
-                    mContext.getString(R.string.from_account_format, accountName));
-        }
-        mAccountTypeTextView.setText(mContext.getString(R.string.account_type_format, accountType));
-        mAccountIcon.setImageDrawable(type.getDisplayIcon(mContext));
-
         mRawContactId = values.getAsLong(RawContacts._ID);
 
+        final ArrayList<Account> accounts =
+                AccountTypeManager.getInstance(mContext).getAccounts(true);
+        if (accounts.size() > 1) {
+            // Fill in the account info
+            String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
+            CharSequence accountType = type.getDisplayLabel(mContext);
+            if (TextUtils.isEmpty(accountType)) {
+                accountType = mContext.getString(R.string.account_phone);
+            }
+            if (!TextUtils.isEmpty(accountName)) {
+                mAccountNameTextView.setText(
+                        mContext.getString(R.string.from_account_format, accountName));
+            }
+            mAccountTypeTextView.setText(
+                    mContext.getString(R.string.account_type_format, accountType));
+            mAccountIcon.setImageDrawable(type.getDisplayIcon(mContext));
+        } else {
+            mAccountContainer.setVisibility(View.GONE);
+        }
+
         // Show photo editor when supported
         EntityModifier.ensureKindExists(state, type, Photo.CONTENT_ITEM_TYPE);
         setHasPhotoEditor((type.getKindForMimetype(Photo.CONTENT_ITEM_TYPE) != null));
diff --git a/src/com/android/contacts/group/GroupBrowseListAdapter.java b/src/com/android/contacts/group/GroupBrowseListAdapter.java
new file mode 100644
index 0000000..ed69776
--- /dev/null
+++ b/src/com/android/contacts/group/GroupBrowseListAdapter.java
@@ -0,0 +1,75 @@
+/*
+ * 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.group;
+
+import com.android.contacts.R;
+import com.android.contacts.GroupMetaData;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.List;
+
+/**
+ * Adapter to populate the list of groups.
+ */
+public class GroupBrowseListAdapter extends BaseAdapter {
+
+    private LayoutInflater mLayoutInflater;
+    private List<GroupMetaData> mGroupList;
+
+    public GroupBrowseListAdapter(Context context, List<GroupMetaData> groupList) {
+        mLayoutInflater = LayoutInflater.from(context);
+        mGroupList = groupList;
+    }
+
+    @Override
+    public int getCount() {
+        return mGroupList.size();
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return getItem(position).getGroupId();
+    }
+
+    @Override
+    public GroupMetaData getItem(int position) {
+        return mGroupList.get(position);
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        if (convertView == null) {
+            convertView = mLayoutInflater.inflate(R.layout.group_browse_list_item, parent, false);
+        }
+        GroupMetaData group = getItem(position);
+        ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
+        TextView label = (TextView) convertView.findViewById(R.id.label);
+        TextView account = (TextView) convertView.findViewById(R.id.account);
+        icon.setImageResource(R.drawable.ic_menu_display_all_holo_light);
+        label.setText(group.getTitle());
+        account.setText(group.getAccountName());
+        return convertView;
+    }
+
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/group/GroupBrowseListFragment.java b/src/com/android/contacts/group/GroupBrowseListFragment.java
new file mode 100644
index 0000000..978ce13
--- /dev/null
+++ b/src/com/android/contacts/group/GroupBrowseListFragment.java
@@ -0,0 +1,172 @@
+/*
+ * 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.group;
+
+import com.android.contacts.GroupMetaData;
+import com.android.contacts.GroupMetaDataLoader;
+import com.android.contacts.R;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.LoaderManager;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.Loader;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnFocusChangeListener;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Fragment to display the list of groups.
+ */
+public class GroupBrowseListFragment extends Fragment
+        implements OnFocusChangeListener, OnTouchListener {
+
+    private static final String TAG = "GroupBrowseListFragment";
+
+    private static final int LOADER_GROUPS = 1;
+
+    private Context mContext;
+    private Cursor mGroupListCursor;
+    private List<GroupMetaData> mGroupList = new ArrayList<GroupMetaData>();
+
+    private View mRootView;
+    private ListView mListView;
+    private View mEmptyView;
+
+    public GroupBrowseListFragment() {
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        mRootView = inflater.inflate(R.layout.group_browse_list_fragment, null);
+        mListView = (ListView) mRootView.findViewById(R.id.list);
+        mListView.setOnFocusChangeListener(this);
+        mListView.setOnTouchListener(this);
+        mEmptyView = mRootView.findViewById(R.id.empty);
+        return mRootView;
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        mContext = activity;
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mContext = null;
+    }
+
+    @Override
+    public void onStart() {
+        getLoaderManager().initLoader(LOADER_GROUPS, null, mGroupLoaderListener);
+        super.onStart();
+    }
+
+    /**
+     * The listener for the group meta data loader.
+     */
+    private final LoaderManager.LoaderCallbacks<Cursor> mGroupLoaderListener =
+            new LoaderCallbacks<Cursor>() {
+
+        @Override
+        public CursorLoader onCreateLoader(int id, Bundle args) {
+            return new GroupMetaDataLoader(mContext);
+        }
+
+        @Override
+        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+            mGroupListCursor = data;
+            bindGroupList();
+        }
+
+        public void onLoaderReset(Loader<Cursor> loader) {
+        }
+    };
+
+    private void bindGroupList() {
+        if (mGroupListCursor == null) {
+            return;
+        }
+        mGroupList.clear();
+        mGroupListCursor.moveToPosition(-1);
+        while (mGroupListCursor.moveToNext()) {
+            String accountName = mGroupListCursor.getString(GroupMetaDataLoader.ACCOUNT_NAME);
+            String accountType = mGroupListCursor.getString(GroupMetaDataLoader.ACCOUNT_TYPE);
+            long groupId = mGroupListCursor.getLong(GroupMetaDataLoader.GROUP_ID);
+            String title = mGroupListCursor.getString(GroupMetaDataLoader.TITLE);
+            boolean defaultGroup = mGroupListCursor.isNull(GroupMetaDataLoader.AUTO_ADD)
+                    ? false
+                    : mGroupListCursor.getInt(GroupMetaDataLoader.AUTO_ADD) != 0;
+            boolean favorites = mGroupListCursor.isNull(GroupMetaDataLoader.FAVORITES)
+                    ? false
+                    : mGroupListCursor.getInt(GroupMetaDataLoader.FAVORITES) != 0;
+
+            // TODO: Separate groups according to account name and type.
+            mGroupList.add(new GroupMetaData(
+                    accountName, accountType, groupId, title, defaultGroup, favorites));
+        }
+
+        mListView.setAdapter(new GroupBrowseListAdapter(mContext, mGroupList));
+        mListView.setEmptyView(mEmptyView);
+    }
+
+    private void hideSoftKeyboard() {
+        if (mContext == null) {
+            return;
+        }
+        // Hide soft keyboard, if visible
+        InputMethodManager inputMethodManager = (InputMethodManager)
+                mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+        inputMethodManager.hideSoftInputFromWindow(mListView.getWindowToken(), 0);
+    }
+
+    /**
+     * Dismisses the soft keyboard when the list takes focus.
+     */
+    @Override
+    public void onFocusChange(View view, boolean hasFocus) {
+        if (view == mListView && hasFocus) {
+            hideSoftKeyboard();
+        }
+    }
+
+    /**
+     * Dismisses the soft keyboard when the list is touched.
+     */
+    @Override
+    public boolean onTouch(View view, MotionEvent event) {
+        if (view == mListView) {
+            hideSoftKeyboard();
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index c85f582..fcced62 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -821,6 +821,7 @@
         }
     }
 
+    @Override
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
         hideSoftKeyboard();
 
@@ -840,6 +841,7 @@
     /**
      * Dismisses the soft keyboard when the list takes focus.
      */
+    @Override
     public void onFocusChange(View view, boolean hasFocus) {
         if (view == mListView && hasFocus) {
             hideSoftKeyboard();
@@ -849,6 +851,7 @@
     /**
      * Dismisses the soft keyboard when the list is touched.
      */
+    @Override
     public boolean onTouch(View view, MotionEvent event) {
         if (view == mListView) {
             hideSoftKeyboard();
diff --git a/src/com/android/contacts/util/AccountsListAdapter.java b/src/com/android/contacts/util/AccountsListAdapter.java
index 8dbfc8d..97a9f84 100644
--- a/src/com/android/contacts/util/AccountsListAdapter.java
+++ b/src/com/android/contacts/util/AccountsListAdapter.java
@@ -60,7 +60,9 @@
         final AccountType accountType = mAccountTypes.getAccountType(account.type);
 
         text1.setText(account.name);
-        text2.setText(accountType.getDisplayLabel(mContext));
+        if (text2 != null) {
+            text2.setText(accountType.getDisplayLabel(mContext));
+        }
         icon.setImageDrawable(accountType.getDisplayIcon(mContext));
 
         return resultView;