merge in jb-release history after reset to jb-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 33da473..996bc78 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -121,7 +121,7 @@
             android:launchMode="singleTask"
             android:clearTaskOnLaunch="true"
             android:icon="@mipmap/ic_launcher_phone"
-            android:screenOrientation="nosensor"
+            android:screenOrientation="portrait"
             android:enabled="@*android:bool/config_voice_capable"
             android:taskAffinity="android.task.contacts.phone"
             android:windowSoftInputMode="stateAlwaysHidden|adjustNothing">
diff --git a/res/layout-sw580dp-w940dp/contact_detail_fragment.xml b/res/layout-sw580dp-w940dp/contact_detail_fragment.xml
index cf89727..9f32584 100644
--- a/res/layout-sw580dp-w940dp/contact_detail_fragment.xml
+++ b/res/layout-sw580dp-w940dp/contact_detail_fragment.xml
@@ -20,8 +20,7 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@color/background_primary"
-    android:paddingRight="16dip">
+    android:background="@color/background_primary">
 
     <!-- Placeholder for empty list -->
     <include
@@ -51,7 +50,9 @@
             android:clipToPadding="false"
             android:fadingEdge="none"
             android:layout_weight="1"
-            android:divider="@null"/>
+            android:divider="@null"
+            android:scrollbarStyle="outsideOverlay"
+            android:paddingRight="16dip"/>
 
     </LinearLayout>
 
diff --git a/res/layout-sw580dp-w940dp/contact_detail_updates_fragment.xml b/res/layout-sw580dp-w940dp/contact_detail_updates_fragment.xml
index 3bcce3f..3b4b422 100644
--- a/res/layout-sw580dp-w940dp/contact_detail_updates_fragment.xml
+++ b/res/layout-sw580dp-w940dp/contact_detail_updates_fragment.xml
@@ -24,4 +24,5 @@
     android:paddingTop="@dimen/contact_detail_list_top_padding"
     android:paddingLeft="16dip"
     android:paddingRight="16dip"
+    android:scrollbarStyle="outsideOverlay"
     android:clipToPadding="false"/>
diff --git a/res/layout-sw580dp/contact_detail_fragment.xml b/res/layout-sw580dp/contact_detail_fragment.xml
index 32b7cbb..fd6390e 100644
--- a/res/layout-sw580dp/contact_detail_fragment.xml
+++ b/res/layout-sw580dp/contact_detail_fragment.xml
@@ -35,7 +35,8 @@
         android:fadingEdge="none"
         android:cacheColorHint="#00000000"
         android:divider="@null"
-    />
+        android:scrollbarStyle="outsideOverlay"
+        android:paddingRight="12dip" />
 
     <!-- "QuickFix"- button (Copy to local contact, add to group) -->
     <Button
diff --git a/res/layout-sw580dp/contact_detail_updates_fragment.xml b/res/layout-sw580dp/contact_detail_updates_fragment.xml
index d231dbc..a154936 100644
--- a/res/layout-sw580dp/contact_detail_updates_fragment.xml
+++ b/res/layout-sw580dp/contact_detail_updates_fragment.xml
@@ -24,6 +24,8 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:fadingEdge="none"
-        android:divider="@null"/>
+        android:divider="@null"
+        android:scrollbarStyle="outsideOverlay"
+        android:paddingRight="12dip" />
 
 </FrameLayout>
diff --git a/res/layout-sw580dp/quickcontact_activity.xml b/res/layout-sw580dp/quickcontact_activity.xml
index a24ad69..a97d86c 100644
--- a/res/layout-sw580dp/quickcontact_activity.xml
+++ b/res/layout-sw580dp/quickcontact_activity.xml
@@ -15,6 +15,7 @@
 -->
 <view
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:ex="http://schemas.android.com/apk/res/com.android.contacts"
     class="com.android.contacts.quickcontact.FloatingChildLayout"
     android:id="@+id/floating_layout"
     android:layout_width="match_parent"
@@ -26,7 +27,8 @@
         android:id="@android:id/content"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:orientation="vertical">
+        android:padding="32dip"
+        android:orientation="vertical" >
         <FrameLayout
             android:layout_width="360dip"
             android:layout_height="@dimen/quick_contact_photo_container_height">
diff --git a/res/layout-sw580dp/updates_header_contact.xml b/res/layout-sw580dp/updates_header_contact.xml
index cb55a2d..b8b3278 100644
--- a/res/layout-sw580dp/updates_header_contact.xml
+++ b/res/layout-sw580dp/updates_header_contact.xml
@@ -29,7 +29,7 @@
         class="com.android.contacts.widget.ProportionalLayout"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:paddingBottom="8dip"
+        android:layout_marginBottom="8dip"
         ex:ratio="0.6667"
         ex:direction="widthToHeight">
 
diff --git a/res/layout-sw680dp-w1000dp/contact_detail_fragment.xml b/res/layout-sw680dp-w1000dp/contact_detail_fragment.xml
index e1a870a..b3f13a6 100644
--- a/res/layout-sw680dp-w1000dp/contact_detail_fragment.xml
+++ b/res/layout-sw680dp-w1000dp/contact_detail_fragment.xml
@@ -21,8 +21,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="@color/background_primary"
-    android:paddingLeft="16dip"
-    android:paddingRight="16dip">
+    android:paddingLeft="16dip">
 
     <!-- Placeholder for empty list -->
     <include
@@ -52,7 +51,9 @@
             android:clipToPadding="false"
             android:fadingEdge="none"
             android:layout_weight="1"
-            android:divider="@null"/>
+            android:divider="@null"
+            android:paddingRight="16dip"
+            android:scrollbarStyle="outsideOverlay"/>
 
     </LinearLayout>
 
diff --git a/res/layout-sw680dp-w1000dp/contact_detail_updates_fragment.xml b/res/layout-sw680dp-w1000dp/contact_detail_updates_fragment.xml
index 439b8f0..d809258 100644
--- a/res/layout-sw680dp-w1000dp/contact_detail_updates_fragment.xml
+++ b/res/layout-sw680dp-w1000dp/contact_detail_updates_fragment.xml
@@ -24,4 +24,5 @@
     android:paddingTop="@dimen/contact_detail_list_top_padding"
     android:paddingLeft="16dip"
     android:paddingRight="16dip"
+    android:scrollbarStyle="outsideOverlay"
     android:clipToPadding="false"/>
diff --git a/res/layout-sw680dp/contact_detail_fragment.xml b/res/layout-sw680dp/contact_detail_fragment.xml
index 3e4d255..4c6315e 100644
--- a/res/layout-sw680dp/contact_detail_fragment.xml
+++ b/res/layout-sw680dp/contact_detail_fragment.xml
@@ -35,7 +35,8 @@
         android:fadingEdge="none"
         android:cacheColorHint="#00000000"
         android:divider="@null"
-    />
+        android:scrollbarStyle="outsideOverlay"
+        android:paddingRight="12dip" />
 
     <!-- "QuickFix"- button (Copy to local contact, add to group) -->
     <Button
diff --git a/res/layout-sw680dp/contact_detail_updates_fragment.xml b/res/layout-sw680dp/contact_detail_updates_fragment.xml
index 03a2e41..a2d1abb 100644
--- a/res/layout-sw680dp/contact_detail_updates_fragment.xml
+++ b/res/layout-sw680dp/contact_detail_updates_fragment.xml
@@ -24,6 +24,8 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:fadingEdge="none"
-        android:divider="@null"/>
+        android:divider="@null"
+        android:scrollbarStyle="outsideOverlay"
+        android:paddingRight="12dip" />
 
 </FrameLayout>
diff --git a/res/layout/stream_item_container.xml b/res/layout/stream_item_container.xml
index ee32596..c5d2c0e 100644
--- a/res/layout/stream_item_container.xml
+++ b/res/layout/stream_item_container.xml
@@ -29,6 +29,7 @@
         android:paddingLeft="@dimen/detail_update_section_item_horizontal_padding"
         android:paddingRight="@dimen/detail_update_section_item_horizontal_padding"
         android:paddingTop="@dimen/detail_update_section_item_vertical_padding"
+        android:paddingBottom="@dimen/detail_update_section_item_vertical_padding"
         android:background="?android:attr/selectableItemBackground"
         android:orientation="vertical"
         >
@@ -81,7 +82,6 @@
         android:id="@+id/horizontal_divider"
         android:layout_width="match_parent"
         android:layout_height="1px"
-        android:layout_marginTop="@dimen/detail_update_section_item_vertical_padding"
         android:background="?android:attr/dividerHorizontal"
         android:layout_gravity="bottom" />
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c53e03f..5037279 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -16,8 +16,6 @@
 <resources>
     <dimen name="account_selector_popup_width">400dip</dimen>
 
-    <dimen name="photo_action_popup_width">400dip</dimen>
-
     <!-- Top position of quick contact. If this is -1, the vertical position is determined
     based on the source of the request -->
     <dimen name="quick_contact_top_position">48dip</dimen>
@@ -82,6 +80,16 @@
     <!-- Width and height of the contact photo on the contact detail page -->
     <dimen name="detail_contact_photo_size">128dip</dimen>
 
+    <!-- Width and height of the expanded contact photo on the contact detail page -->
+    <dimen name="detail_contact_photo_expanded_size">400dip</dimen>
+
+    <!-- This is the minimum amount of space to leave underneath an expanded contact detail
+         photo -->
+    <dimen name="expanded_photo_height_offset">100dip</dimen>
+
+    <!-- Minimum width for the photo action popup options -->
+    <dimen name="photo_action_popup_min_width">300dip</dimen>
+
     <!-- Left and right padding for a contact detail item -->
     <dimen name="detail_item_icon_margin">8dip</dimen>
 
diff --git a/src/com/android/contacts/activities/ContactSelectionActivity.java b/src/com/android/contacts/activities/ContactSelectionActivity.java
index bf7d7d1..0db3a0c 100644
--- a/src/com/android/contacts/activities/ContactSelectionActivity.java
+++ b/src/com/android/contacts/activities/ContactSelectionActivity.java
@@ -591,6 +591,7 @@
 
     private void startCreateNewContactActivity() {
         Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
+        intent.putExtra(ContactEditorActivity.INTENT_KEY_FINISH_ACTIVITY_ON_SAVE_COMPLETED, true);
         startActivityAndForwardResult(intent);
     }
 
diff --git a/src/com/android/contacts/activities/PhotoSelectionActivity.java b/src/com/android/contacts/activities/PhotoSelectionActivity.java
index 0610bb6..21cf192 100644
--- a/src/com/android/contacts/activities/PhotoSelectionActivity.java
+++ b/src/com/android/contacts/activities/PhotoSelectionActivity.java
@@ -37,7 +37,6 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcelable;
-import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
 import android.widget.FrameLayout.LayoutParams;
@@ -105,6 +104,15 @@
     /** Whether to animate the photo to an expanded view covering more of the screen. */
     private boolean mExpandPhoto;
 
+    /**
+     * Side length (in pixels) of the expanded photo if to be expanded. Photos are expected to
+     * be square.
+     */
+    private int mExpandedPhotoSize;
+
+    /** Height (in pixels) to leave underneath the expanded photo to show the list popup */
+    private int mHeightOffset;
+
     /** The semi-transparent backdrop. */
     private View mBackdrop;
 
@@ -164,6 +172,12 @@
         mIsDirectoryContact = intent.getBooleanExtra(IS_DIRECTORY_CONTACT, false);
         mExpandPhoto = intent.getBooleanExtra(EXPAND_PHOTO, false);
 
+        // Pull out photo expansion properties from resources
+        mExpandedPhotoSize = getResources().getDimensionPixelSize(
+                R.dimen.detail_contact_photo_expanded_size);
+        mHeightOffset = getResources().getDimensionPixelOffset(
+                R.dimen.expanded_photo_height_offset);
+
         mBackdrop = findViewById(R.id.backdrop);
         mPhotoView = (ImageView) findViewById(R.id.photo);
         mSourceBounds = intent.getSourceBounds();
@@ -188,6 +202,30 @@
         });
     }
 
+    /**
+     * Compute the adjusted expanded photo size to fit within the enclosing view with the same
+     * aspect ratio.
+     * @param enclosingView This is the view that the photo must fit within.
+     * @param heightOffset This is the amount of height to leave open for the photo action popup.
+     */
+    private int getAdjustedExpandedPhotoSize(View enclosingView, int heightOffset) {
+        // pull out the bounds of the backdrop
+        final Rect bounds = new Rect();
+        enclosingView.getDrawingRect(bounds);
+        final int boundsWidth = bounds.width();
+        final int boundsHeight = bounds.height() - heightOffset;
+
+        // ensure that the new expanded photo size can fit within the backdrop
+        final float alpha = Math.min((float) boundsHeight / (float) mExpandedPhotoSize,
+                (float) boundsWidth / (float) mExpandedPhotoSize);
+        if (alpha < 1.0f) {
+            // need to shrink width and height while maintaining aspect ratio
+            return (int) (alpha * mExpandedPhotoSize);
+        } else {
+            return mExpandedPhotoSize;
+        }
+    }
+
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
@@ -279,7 +317,6 @@
 
         // Load the photo.
         int photoWidth = getPhotoEndParams().width;
-        Log.d(TAG, "Photo width: " + photoWidth);
         if (mPhotoUri != null) {
             // If we have a URI, the bitmap should be cached directly.
             ContactPhotoManager.getInstance(this).loadPhoto(mPhotoView, mPhotoUri, photoWidth,
@@ -317,25 +354,32 @@
         attachPhotoHandler();
     }
 
+    /**
+     * This sets the photo's layout params at the end of the animation.
+     * <p>
+     * The scheme is to enlarge the photo to the desired size with the enlarged photo shifted
+     * to the top left of the screen as much as possible while keeping the underlying smaller
+     * photo occluded.
+     */
     private LayoutParams getPhotoEndParams() {
         if (mPhotoEndParams == null) {
             mPhotoEndParams = new LayoutParams(mPhotoStartParams);
             if (mExpandPhoto) {
-                Rect bounds = new Rect();
-                mBackdrop.getDrawingRect(bounds);
-                if (bounds.height() > bounds.width()) {
-                    //Take up full width.
-                    mPhotoEndParams.width = bounds.width();
-                    mPhotoEndParams.height = bounds.width();
-                } else {
-                    // Take up full height, leaving space for the popup.
-                    mPhotoEndParams.height = bounds.height() - 150;
-                    mPhotoEndParams.width = bounds.height() - 150;
+                final int adjustedPhotoSize = getAdjustedExpandedPhotoSize(mBackdrop,
+                        mHeightOffset);
+                int widthDelta = adjustedPhotoSize - mPhotoStartParams.width;
+                int heightDelta = adjustedPhotoSize - mPhotoStartParams.height;
+                if (widthDelta >= 1 || heightDelta >= 1) {
+                    // This is an actual expansion.
+                    mPhotoEndParams.width = adjustedPhotoSize;
+                    mPhotoEndParams.height = adjustedPhotoSize;
+                    mPhotoEndParams.topMargin =
+                            Math.max(mPhotoStartParams.topMargin - heightDelta, 0);
+                    mPhotoEndParams.leftMargin =
+                            Math.max(mPhotoStartParams.leftMargin - widthDelta, 0);
+                    mPhotoEndParams.bottomMargin = 0;
+                    mPhotoEndParams.rightMargin = 0;
                 }
-                mPhotoEndParams.topMargin = 0;
-                mPhotoEndParams.leftMargin = 0;
-                mPhotoEndParams.bottomMargin = mPhotoEndParams.height;
-                mPhotoEndParams.rightMargin = mPhotoEndParams.width;
             }
         }
         return mPhotoEndParams;
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index 29aa51a..c91f493 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -298,7 +298,6 @@
         mPhotoTouchOverlay = mView.findViewById(R.id.photo_touch_intercept_overlay);
 
         mListView = (ListView) mView.findViewById(android.R.id.list);
-        mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY);
         mListView.setOnItemClickListener(this);
         mListView.setItemsCanFocus(true);
         mListView.setOnScrollListener(mVerticalScrollListener);
@@ -425,12 +424,14 @@
             // updates or not.
             if (mShowStaticPhoto) {
                 mStaticPhotoContainer.setVisibility(View.VISIBLE);
-                ImageView photoView = (ImageView) mStaticPhotoContainer.findViewById(R.id.photo);
-                OnClickListener listener = mPhotoSetter.setupContactPhotoForClick(
-                        mContext, mContactData, photoView, false);
+                final ImageView photoView = (ImageView) mStaticPhotoContainer.findViewById(
+                        R.id.photo);
+                final boolean expandPhotoOnClick = mContactData.getPhotoUri() != null;
+                final OnClickListener listener = mPhotoSetter.setupContactPhotoForClick(
+                        mContext, mContactData, photoView, expandPhotoOnClick);
                 if (mPhotoTouchOverlay != null) {
                     mPhotoTouchOverlay.setVisibility(View.VISIBLE);
-                    if (mContactData.isWritableContact(mContext)) {
+                    if (expandPhotoOnClick || mContactData.isWritableContact(mContext)) {
                         mPhotoTouchOverlay.setOnClickListener(listener);
                     } else {
                         mPhotoTouchOverlay.setClickable(false);
@@ -1524,8 +1525,8 @@
 
             // Set the photo if it should be displayed
             if (viewCache.photoView != null) {
-                final boolean expandOnClick = !PhoneCapabilityTester.isUsingTwoPanes(mContext);
-                OnClickListener listener = mPhotoSetter.setupContactPhotoForClick(
+                final boolean expandOnClick = mContactData.getPhotoUri() != null;
+                final OnClickListener listener = mPhotoSetter.setupContactPhotoForClick(
                         mContext, mContactData, viewCache.photoView, expandOnClick);
 
                 if (expandOnClick || mContactData.isWritableContact(mContext)) {
diff --git a/src/com/android/contacts/detail/ContactDetailPhotoSetter.java b/src/com/android/contacts/detail/ContactDetailPhotoSetter.java
index b674dc0..dffb37b 100644
--- a/src/com/android/contacts/detail/ContactDetailPhotoSetter.java
+++ b/src/com/android/contacts/detail/ContactDetailPhotoSetter.java
@@ -72,6 +72,7 @@
             final int[] pos = new int[2];
             v.getLocationOnScreen(pos);
 
+            // rect is the bounds (in pixels) of the photo view in screen coordinates
             final Rect rect = new Rect();
             rect.left = (int) (pos[0] * appScale + 0.5f);
             rect.top = (int) (pos[1] * appScale + 0.5f);
diff --git a/src/com/android/contacts/detail/ContactDetailTabCarousel.java b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
index dd0723d..3929281 100644
--- a/src/com/android/contacts/detail/ContactDetailTabCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
@@ -472,8 +472,8 @@
 
         // TODO: Move this into the {@link CarouselTab} class when the updates
         // fragment code is more finalized.
-        final boolean expandOnClick = !PhoneCapabilityTester.isUsingTwoPanes(mContext);
-        OnClickListener listener = mPhotoSetter.setupContactPhotoForClick(
+        final boolean expandOnClick = contactData.getPhotoUri() != null;
+        final OnClickListener listener = mPhotoSetter.setupContactPhotoForClick(
                 mContext, contactData, mPhotoView, expandOnClick);
 
         if (expandOnClick || contactData.isWritableContact(mContext)) {
diff --git a/src/com/android/contacts/dialog/ClearFrequentsDialog.java b/src/com/android/contacts/dialog/ClearFrequentsDialog.java
index ecb1a27..4aeaf1c 100644
--- a/src/com/android/contacts/dialog/ClearFrequentsDialog.java
+++ b/src/com/android/contacts/dialog/ClearFrequentsDialog.java
@@ -45,6 +45,9 @@
         final OnClickListener okListener = new OnClickListener() {
             @Override
             public void onClick(DialogInterface dialog, int which) {
+                final IndeterminateProgressDialog progressDialog = IndeterminateProgressDialog.show(
+                        getFragmentManager(), getString(R.string.clearFrequentsProgress_title),
+                        null, 500);
                 final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
                     @Override
                     protected Void doInBackground(Void... params) {
@@ -52,8 +55,13 @@
                                 null, null);
                         return null;
                     }
+
+                    @Override
+                    protected void onPostExecute(Void result) {
+                        progressDialog.dismiss();
+                    }
                 };
-                task.execute();
+                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
             }
         };
         return new AlertDialog.Builder(getActivity())
diff --git a/src/com/android/contacts/dialog/IndeterminateProgressDialog.java b/src/com/android/contacts/dialog/IndeterminateProgressDialog.java
new file mode 100644
index 0000000..21cd4bb
--- /dev/null
+++ b/src/com/android/contacts/dialog/IndeterminateProgressDialog.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2012 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.dialog;
+
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Handler;
+
+/**
+ * Indeterminate progress dialog wrapped up in a DialogFragment to work even when the device
+ * orientation is changed. Currently, only supports adding a title and/or message to the progress
+ * dialog.  There is an additional parameter of the minimum amount of time to display the progress
+ * dialog even after a call to dismiss the dialog {@link #dismiss()} or
+ * {@link #dismissAllowingStateLoss()}.
+ * <p>
+ * To create and show the progress dialog, use
+ * {@link #show(FragmentManager, CharSequence, CharSequence, long)} and retain the reference to the
+ * IndeterminateProgressDialog instance.
+ * <p>
+ * To dismiss the dialog, use {@link #dismiss()} or {@link #dismissAllowingStateLoss()} on the
+ * instance.  The instance returned by
+ * {@link #show(FragmentManager, CharSequence, CharSequence, long)} is guaranteed to be valid
+ * after a device orientation change because the {@link #setRetainInstance(boolean)} is called
+ * internally with true.
+ */
+public class IndeterminateProgressDialog extends DialogFragment {
+    private static final String TAG = IndeterminateProgressDialog.class.getSimpleName();
+
+    private CharSequence mTitle;
+    private CharSequence mMessage;
+    private long mMinDisplayTime;
+    private long mShowTime = 0;
+    private boolean mActivityReady = false;
+    private Dialog mOldDialog;
+    private final Handler mHandler = new Handler();
+    private boolean mCalledSuperDismiss = false;
+    private boolean mAllowStateLoss;
+    private final Runnable mDismisser = new Runnable() {
+        @Override
+        public void run() {
+            superDismiss();
+        }
+    };
+
+    /**
+     * Creates and shows an indeterminate progress dialog.  Once the progress dialog is shown, it
+     * will be shown for at least the minDisplayTime (in milliseconds), so that the progress dialog
+     * does not flash in and out to quickly.
+     */
+    public static IndeterminateProgressDialog show(FragmentManager fragmentManager,
+            CharSequence title, CharSequence message, long minDisplayTime) {
+        IndeterminateProgressDialog dialogFragment = new IndeterminateProgressDialog();
+        dialogFragment.mTitle = title;
+        dialogFragment.mMessage = message;
+        dialogFragment.mMinDisplayTime = minDisplayTime;
+        dialogFragment.show(fragmentManager, TAG);
+        dialogFragment.mShowTime = System.currentTimeMillis();
+        dialogFragment.setCancelable(false);
+
+        return dialogFragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setRetainInstance(true);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Create the progress dialog and set its properties
+        final ProgressDialog dialog = new ProgressDialog(getActivity());
+        dialog.setIndeterminate(true);
+        dialog.setIndeterminateDrawable(null);
+        dialog.setTitle(mTitle);
+        dialog.setMessage(mMessage);
+
+        return dialog;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mActivityReady = true;
+
+        // Check if superDismiss() had been called before.  This can happen if in a long
+        // running operation, the user hits the home button and closes this fragment's activity.
+        // Upon returning, we want to dismiss this progress dialog fragment.
+        if (mCalledSuperDismiss) {
+            superDismiss();
+        }
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mActivityReady = false;
+    }
+
+    /**
+     * There is a race condition that is not handled properly by the DialogFragment class.
+     * If we don't check that this onDismiss callback isn't for the old progress dialog from before
+     * the device orientation change, then this will cause the newly created dialog after the
+     * orientation change to be dismissed immediately.
+     */
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        if (mOldDialog != null && mOldDialog == dialog) {
+            // This is the callback from the old progress dialog that was already dismissed before
+            // the device orientation change, so just ignore it.
+            return;
+        }
+        super.onDismiss(dialog);
+    }
+
+    /**
+     * Save the old dialog that is about to get destroyed in case this is due to a change
+     * in device orientation.  This will allow us to intercept the callback to
+     * {@link #onDismiss(DialogInterface)} in case the callback happens after a new progress dialog
+     * instance was created.
+     */
+    @Override
+    public void onDestroyView() {
+        mOldDialog = getDialog();
+        super.onDestroyView();
+    }
+
+    /**
+     * This tells the progress dialog to dismiss itself after guaranteeing to be shown for the
+     * specified time in {@link #show(FragmentManager, CharSequence, CharSequence, long)}.
+     */
+    @Override
+    public void dismiss() {
+        mAllowStateLoss = false;
+        dismissWhenReady();
+    }
+
+    /**
+     * This tells the progress dialog to dismiss itself (with state loss) after guaranteeing to be
+     * shown for the specified time in
+     * {@link #show(FragmentManager, CharSequence, CharSequence, long)}.
+     */
+    @Override
+    public void dismissAllowingStateLoss() {
+        mAllowStateLoss = true;
+        dismissWhenReady();
+    }
+
+    /**
+     * Tells the progress dialog to dismiss itself after guaranteeing that the dialog had been
+     * showing for at least the minimum display time as set in
+     * {@link #show(FragmentManager, CharSequence, CharSequence, long)}.
+     */
+    private void dismissWhenReady() {
+        // Compute how long the dialog has been showing
+        final long shownTime = System.currentTimeMillis() - mShowTime;
+        if (shownTime >= mMinDisplayTime) {
+            // dismiss immediately
+            mHandler.post(mDismisser);
+        } else {
+            // Need to wait some more, so compute the amount of time to sleep.
+            final long sleepTime = mMinDisplayTime - shownTime;
+            mHandler.postDelayed(mDismisser, sleepTime);
+        }
+    }
+
+    /**
+     * Actually dismiss the dialog fragment.
+     */
+    private void superDismiss() {
+        mCalledSuperDismiss = true;
+        if (mActivityReady) {
+            // The fragment is either in onStart or past it, but has not gotten to onStop yet.
+            // It is safe to dismiss this dialog fragment.
+            if (mAllowStateLoss) {
+                super.dismissAllowingStateLoss();
+            } else {
+                super.dismiss();
+            }
+        }
+        // If mActivityReady is false, then this dialog fragment has already passed the onStop
+        // state. This can happen if the user hit the 'home' button before this dialog fragment was
+        // dismissed or if there is a configuration change.
+        // In the event that this dialog fragment is re-attached and reaches onStart (e.g.,
+        // because the user returns to this fragment's activity or the device configuration change
+        // has re-attached this dialog fragment), because the mCalledSuperDismiss flag was set to
+        // true, this dialog fragment will be dismissed within onStart.  So, there's nothing else
+        // that needs to be done.
+    }
+}
diff --git a/src/com/android/contacts/editor/PhotoActionPopup.java b/src/com/android/contacts/editor/PhotoActionPopup.java
index 9744308..a27dd8a 100644
--- a/src/com/android/contacts/editor/PhotoActionPopup.java
+++ b/src/com/android/contacts/editor/PhotoActionPopup.java
@@ -126,10 +126,13 @@
         listPopupWindow.setAnchorView(anchorView);
         listPopupWindow.setAdapter(adapter);
         listPopupWindow.setOnItemClickListener(clickListener);
-        listPopupWindow.setWidth(context.getResources().getDimensionPixelSize(
-                R.dimen.photo_action_popup_width));
         listPopupWindow.setModal(true);
         listPopupWindow.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
+        final int minWidth = context.getResources().getDimensionPixelSize(
+                R.dimen.photo_action_popup_min_width);
+        if (anchorView.getWidth() < minWidth) {
+            listPopupWindow.setWidth(minWidth);
+        }
         return listPopupWindow;
     }
 
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
index d947f06..78bf628 100644
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -86,9 +86,17 @@
     private String mContactsCount = "";
     private boolean mDarkTheme = false;
 
+    /** Resource used to provide header-text for default filter. */
+    private CharSequence mDefaultFilterHeaderText;
+
     public ContactEntryListAdapter(Context context) {
         super(context);
         addPartitions();
+        setDefaultFilterHeaderText(R.string.local_search_label);
+    }
+
+    protected void setDefaultFilterHeaderText(int resourceId) {
+        mDefaultFilterHeaderText = getContext().getResources().getText(resourceId);
     }
 
     @Override
@@ -532,7 +540,7 @@
         TextView labelTextView = (TextView)view.findViewById(R.id.label);
         TextView displayNameTextView = (TextView)view.findViewById(R.id.display_name);
         if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
-            labelTextView.setText(R.string.local_search_label);
+            labelTextView.setText(mDefaultFilterHeaderText);
             displayNameTextView.setText(null);
         } else {
             labelTextView.setText(R.string.directory_search_label);
diff --git a/src/com/android/contacts/list/PhoneNumberListAdapter.java b/src/com/android/contacts/list/PhoneNumberListAdapter.java
index bc73f73..6323cc1 100644
--- a/src/com/android/contacts/list/PhoneNumberListAdapter.java
+++ b/src/com/android/contacts/list/PhoneNumberListAdapter.java
@@ -29,12 +29,13 @@
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.Directory;
-import android.provider.ContactsContract.RawContacts;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.contacts.R;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -90,7 +91,7 @@
 
     public PhoneNumberListAdapter(Context context) {
         super(context);
-
+        setDefaultFilterHeaderText(R.string.list_filter_phones);
         mUnknownNameText = context.getText(android.R.string.unknownName);
     }
 
diff --git a/src/com/android/contacts/list/PhoneNumberPickerFragment.java b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
index 7680a97..3785fa6 100644
--- a/src/com/android/contacts/list/PhoneNumberPickerFragment.java
+++ b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
@@ -116,8 +116,10 @@
         if (mAccountFilterHeader == null || filter == null) {
             return;
         }
-        final boolean shouldShowHeader = AccountFilterUtil.updateAccountFilterTitleForPhone(
-                mAccountFilterHeader, filter, false);
+        final boolean shouldShowHeader =
+                !isSearchMode() &&
+                AccountFilterUtil.updateAccountFilterTitleForPhone(
+                        mAccountFilterHeader, filter, false);
         if (shouldShowHeader) {
             mPaddingView.setVisibility(View.GONE);
             mAccountFilterHeader.setVisibility(View.VISIBLE);
diff --git a/src/com/android/contacts/quickcontact/FloatingChildLayout.java b/src/com/android/contacts/quickcontact/FloatingChildLayout.java
index 6302c5d..dca738c 100644
--- a/src/com/android/contacts/quickcontact/FloatingChildLayout.java
+++ b/src/com/android/contacts/quickcontact/FloatingChildLayout.java
@@ -23,15 +23,13 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
 import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.TransitionDrawable;
-import android.os.Handler;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewPropertyAnimator;
 import android.view.animation.AnimationUtils;
 import android.widget.FrameLayout;
 import android.widget.PopupWindow;
@@ -53,11 +51,30 @@
     private static final String TAG = "FloatingChildLayout";
     private int mFixedTopPosition;
     private View mChild;
-    private boolean mIsShowingChild;
     private Rect mTargetScreen = new Rect();
     private final int mAnimationDuration;
     private final TransitionDrawable mBackground;
 
+    /** The phase of the background dim. This is one of the values of {@link BackgroundPhase}  */
+    private int mBackgroundPhase = BackgroundPhase.BEFORE;
+
+    private interface BackgroundPhase {
+        public static final int BEFORE = 0;
+        public static final int APPEARING_OR_VISIBLE = 1;
+        public static final int DISAPPEARING_OR_GONE = 3;
+    }
+
+    /** The phase of the contents window. This is one of the values of {@link ForegroundPhase}  */
+    private int mForegroundPhase = ForegroundPhase.BEFORE;
+
+    private interface ForegroundPhase {
+        public static final int BEFORE = 0;
+        public static final int APPEARING = 1;
+        public static final int IDLE = 2;
+        public static final int DISAPPEARING = 3;
+        public static final int AFTER = 4;
+    }
+
     // Black, 50% alpha as per the system default.
     private static final int DIM_BACKGROUND_COLOR = 0x7F000000;
 
@@ -83,8 +100,6 @@
         mChild.setScaleX(0.5f);
         mChild.setScaleY(0.5f);
         mChild.setAlpha(0.0f);
-
-        mIsShowingChild = false;
     }
 
     public View getChild() {
@@ -163,43 +178,51 @@
         child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
     }
 
-    /** Begin animating {@link #getChild()} visible. */
-    public void showChild(final Runnable onAnimationEndRunnable) {
-        if (mIsShowingChild) return;
-        mIsShowingChild = true;
-
-        // TODO: understand this.
-        // For some reason this needs wait a tick in order to avoid jank.
-        // Maybe because we set up a hardware layer in animateScale()?
-        // Probably not, since it should also be required in hideChild().
-        new Handler().post(new Runnable() {
-            @Override public void run() {
-                animateBackground(false);
-            }
-        });
-
-        animateScale(false, onAnimationEndRunnable);
-    }
-
-    /** Begin animating {@link #getChild()} invisible. */
-    public void hideChild(final Runnable onAnimationEndRunnable) {
-        if (!mIsShowingChild) return;
-        mIsShowingChild = false;
-
-        animateBackground(true);
-        animateScale(true, onAnimationEndRunnable);
-    }
-
-    private void animateBackground(boolean isExitAnimation) {
-        if (isExitAnimation) {
-            mBackground.reverseTransition(mAnimationDuration);
-        } else {
+    public void fadeInBackground() {
+        if (mBackgroundPhase == BackgroundPhase.BEFORE) {
+            mBackgroundPhase = BackgroundPhase.APPEARING_OR_VISIBLE;
             mBackground.startTransition(mAnimationDuration);
         }
     }
 
+    public void fadeOutBackground() {
+        if (mBackgroundPhase == BackgroundPhase.APPEARING_OR_VISIBLE) {
+            mBackgroundPhase = BackgroundPhase.DISAPPEARING_OR_GONE;
+            mBackground.reverseTransition(mAnimationDuration);
+        }
+    }
+
+    public boolean isContentFullyVisible() {
+        return mForegroundPhase == ForegroundPhase.IDLE;
+    }
+
+    /** Begin animating {@link #getChild()} visible. */
+    public void showContent(final Runnable onAnimationEndRunnable) {
+        if (mForegroundPhase == ForegroundPhase.BEFORE) {
+            mForegroundPhase = ForegroundPhase.APPEARING;
+            animateScale(false, onAnimationEndRunnable);
+        }
+    }
+
+    /**
+     * Begin animating {@link #getChild()} invisible. Returns false if animation is not valid in
+     * this state
+     */
+    public boolean hideContent(final Runnable onAnimationEndRunnable) {
+        if (mForegroundPhase == ForegroundPhase.APPEARING ||
+                mForegroundPhase == ForegroundPhase.IDLE) {
+            mForegroundPhase = ForegroundPhase.DISAPPEARING;
+            animateScale(true, onAnimationEndRunnable);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     /** Creates the open/close animation */
-    private void animateScale(boolean isExitAnimation, final Runnable onAnimationEndRunnable) {
+    private void animateScale(
+            final boolean isExitAnimation,
+            final Runnable onAnimationEndRunnable) {
         mChild.setPivotX(mTargetScreen.centerX() - mChild.getLeft());
         mChild.setPivotY(mTargetScreen.centerY() - mChild.getTop());
 
@@ -208,7 +231,7 @@
                 : android.R.interpolator.decelerate_quint;
         final float scaleTarget = isExitAnimation ? 0.5f : 1.0f;
 
-        ViewPropertyAnimator animator = mChild.animate().withLayer()
+        mChild.animate().withLayer()
                 .setDuration(mAnimationDuration)
                 .setInterpolator(AnimationUtils.loadInterpolator(getContext(), scaleInterpolator))
                 .scaleX(scaleTarget)
@@ -217,7 +240,17 @@
                 .setListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
-                        if (onAnimationEndRunnable != null) onAnimationEndRunnable.run();
+                        if (isExitAnimation) {
+                            if (mForegroundPhase == ForegroundPhase.DISAPPEARING) {
+                                mForegroundPhase = ForegroundPhase.AFTER;
+                                if (onAnimationEndRunnable != null) onAnimationEndRunnable.run();
+                            }
+                        } else {
+                            if (mForegroundPhase == ForegroundPhase.APPEARING) {
+                                mForegroundPhase = ForegroundPhase.IDLE;
+                                if (onAnimationEndRunnable != null) onAnimationEndRunnable.run();
+                            }
+                        }
                     }
                 });
     }
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index b656969..5fe34e0 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -98,9 +98,6 @@
     private String[] mExcludeMimes;
     private List<String> mSortedActionMimeTypes = Lists.newArrayList();
 
-    private boolean mHasFinishedAnimatingIn = false;
-    private boolean mHasStartedAnimatingOut = false;
-
     private FloatingChildLayout mFloatingLayout;
 
     private View mPhotoContainer;
@@ -154,6 +151,8 @@
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
+        if (TRACE_LAUNCH) android.os.Debug.startMethodTracing(TRACE_TAG);
+
         // Show QuickContact in front of soft input
         getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
@@ -172,7 +171,8 @@
         mFloatingLayout.setOnOutsideTouchListener(new View.OnTouchListener() {
             @Override
             public boolean onTouch(View v, MotionEvent event) {
-                return handleOutsideTouch();
+                handleOutsideTouch();
+                return true;
             }
         });
 
@@ -183,7 +183,7 @@
                 mContactLoader.cacheResult();
                 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
                 startActivity(intent);
-                hide(false);
+                close(false);
             }
         };
         mOpenDetailsButton.setOnClickListener(openDetailsClickHandler);
@@ -191,15 +191,6 @@
         mListPager.setAdapter(new ViewPagerAdapter(getFragmentManager()));
         mListPager.setOnPageChangeListener(new PageChangeListener());
 
-        show();
-    }
-
-    private void show() {
-
-        if (TRACE_LAUNCH) {
-            android.os.Debug.startMethodTracing(TRACE_TAG);
-        }
-
         final Intent intent = getIntent();
 
         Uri lookupUri = intent.getData();
@@ -226,23 +217,23 @@
 
         mContactLoader = (ContactLoader) getLoaderManager().initLoader(
                 LOADER_ID, null, mLoaderCallbacks);
+
+        mFloatingLayout.fadeInBackground();
     }
 
-    private boolean handleOutsideTouch() {
-        if (!mHasFinishedAnimatingIn) return false;
-        if (mHasStartedAnimatingOut) return false;
-
-        mHasStartedAnimatingOut = true;
-        hide(true);
-        return true;
+    private void handleOutsideTouch() {
+        if (mFloatingLayout.isContentFullyVisible()) {
+            close(true);
+        }
     }
 
-    private void hide(boolean withAnimation) {
+    private void close(boolean withAnimation) {
         // cancel any pending queries
         getLoaderManager().destroyLoader(LOADER_ID);
 
         if (withAnimation) {
-            mFloatingLayout.hideChild(new Runnable() {
+            mFloatingLayout.fadeOutBackground();
+            final boolean animated = mFloatingLayout.hideContent(new Runnable() {
                 @Override
                 public void run() {
                     // Wait until the final animation frame has been drawn, otherwise
@@ -266,15 +257,19 @@
                     });
                 }
             });
+            if (!animated) {
+                // If we were in the wrong state, simply quit (this can happen for example
+                // if the user pushes BACK before anything has loaded)
+                finish();
+            }
         } else {
-            mFloatingLayout.hideChild(null);
             finish();
         }
     }
 
     @Override
     public void onBackPressed() {
-        hide(true);
+        close(true);
     }
 
     /** Assign this string to the view if it is not empty. */
@@ -480,7 +475,7 @@
         @Override
         public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) {
             if (isFinishing()) {
-                hide(false);
+                close(false);
                 return;
             }
             if (data.isError()) {
@@ -492,25 +487,22 @@
                 Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri());
                 Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage,
                         Toast.LENGTH_LONG).show();
-                hide(false);
+                close(false);
                 return;
             }
 
             bindData(data);
 
-            if (TRACE_LAUNCH) {
-                android.os.Debug.stopMethodTracing();
-            }
+            if (TRACE_LAUNCH) android.os.Debug.stopMethodTracing();
 
             // Data bound and ready, pull curtain to show. Put this on the Handler to ensure
             // that the layout passes are completed
             SchedulingUtils.doAfterLayout(mFloatingLayout, new Runnable() {
                 @Override
                 public void run() {
-                    mFloatingLayout.showChild(new Runnable() {
+                    mFloatingLayout.showContent(new Runnable() {
                         @Override
                         public void run() {
-                            mHasFinishedAnimatingIn = true;
                             mContactLoader.upgradeToFullContact();
                         }
                     });
@@ -599,7 +591,7 @@
                                 Toast.LENGTH_SHORT).show();
                     }
 
-                    hide(false);
+                    close(false);
                 }
             };
             // Defer the action to make the window properly repaint
diff --git a/src/com/android/contacts/socialwidget/SocialWidgetConfigureActivity.java b/src/com/android/contacts/socialwidget/SocialWidgetConfigureActivity.java
index 98812d9..39307bf 100644
--- a/src/com/android/contacts/socialwidget/SocialWidgetConfigureActivity.java
+++ b/src/com/android/contacts/socialwidget/SocialWidgetConfigureActivity.java
@@ -23,12 +23,19 @@
 import android.provider.ContactsContract.Contacts;
 
 public class SocialWidgetConfigureActivity extends Activity {
+    private static final String KEY_LAUNCHED = "already_launched_picker_activity";
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         // If the user presses back, we want to cancel
         setResult(RESULT_CANCELED);
 
+        // Don't launch contact-picker if we already launched it (for example, if
+        // we launched it in a previous onCreate() and the device orientation changes
+        // before the picker returns its result, then this activity will be recreated).
+        if (savedInstanceState != null && savedInstanceState.getBoolean(KEY_LAUNCHED)) return;
+
         // Forward the Intent to the picker
         final Intent pickerIntent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
         pickerIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -36,6 +43,14 @@
     }
 
     @Override
+    protected void onSaveInstanceState(Bundle savedInstanceState) {
+        super.onSaveInstanceState(savedInstanceState);
+
+        // We know for sure that we've launched the contact-picker... see onCreate()
+        savedInstanceState.putBoolean(KEY_LAUNCHED, true);
+    }
+
+    @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         // We came back from the Picker. If the user actually selected a contact,
         // return it now