Move QuickContact to Activity instead of Window.

QuickContact has traditionally used a Window to show a bubble with
callout centered around a target area.  This change moves away from
private Window APIs, and instead creates FloatingChildLayout to layout
the bubble.  Using onLayout() is more flexible than a PopupWindow or
Dialog, since it gives us access to getWindowVisibleDisplayFrame() to
correctly handle system windows.  (Similar to FLAG_LAYOUT_INSET_DECOR.)

Changes background Drawable to use state_first and state_last to select
above/below callout arrow.  Also moves to using setLevel() to set arrow
horizontal location.

Removes recycling chiclet code, and brings in Guava library.

Bug: 3362647
Change-Id: Iae953bae71db76e91e05996fe4c0dcea38bb446f
diff --git a/Android.mk b/Android.mk
index ff1b7e2..1c9dcc4 100644
--- a/Android.mk
+++ b/Android.mk
@@ -5,7 +5,11 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := com.android.phone.common com.android.vcard android-common
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    com.android.phone.common \
+    com.android.vcard \
+    android-common \
+    guava
 
 LOCAL_PACKAGE_NAME := Contacts
 LOCAL_CERTIFICATE := shared
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 20641f2..458553e 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -326,7 +326,7 @@
 
         <activity
             android:name=".activities.ShowOrCreateActivity"
-            android:theme="@style/FullyTranslucent">
+            android:theme="@android:style/Theme.Translucent.NoTitleBar">
 
             <intent-filter>
                 <action android:name="com.android.contacts.action.SHOW_OR_CREATE_CONTACT" />
@@ -339,13 +339,12 @@
         <!-- Used to show QuickContact window over a translucent activity, which is a
              temporary hack until we add better framework support. -->
         <activity
-            android:name=".quickcontact.QuickContactActivity"
-            android:theme="@style/FullyTranslucent.QuickContact"
+            android:name=".quickcontact.QuickContactWindow"
+            android:theme="@style/Theme.QuickContact"
             android:launchMode="singleTop"
             android:excludeFromRecents="true"
             android:taskAffinity="android.task.quickcontact"
-            android:windowSoftInputMode="stateUnchanged"
-            >
+            android:windowSoftInputMode="stateUnchanged">
 
             <intent-filter>
                 <action android:name="com.android.contacts.action.QUICK_CONTACT" />
diff --git a/res/drawable/quickactions_arrow_left_holo_light.xml b/res/drawable/quickactions_arrow_left_holo_light.xml
new file mode 100644
index 0000000..c1e18bd
--- /dev/null
+++ b/res/drawable/quickactions_arrow_left_holo_light.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_first="true" android:drawable="@drawable/quickactions_arrowdown_left_holo_light" />
+    <item android:state_last="true" android:drawable="@drawable/quickactions_arrowup_left_holo_light" />
+    <!-- TODO: provide callout-less state -->
+    <item android:drawable="@drawable/quickactions_arrowup_left_holo_light" />
+</selector>
diff --git a/res/drawable/quickactions_arrow_middle_holo_light.xml b/res/drawable/quickactions_arrow_middle_holo_light.xml
new file mode 100644
index 0000000..f88b513
--- /dev/null
+++ b/res/drawable/quickactions_arrow_middle_holo_light.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_first="true" android:drawable="@drawable/quickactions_arrowdown_middle_holo_light" />
+    <item android:state_last="true" android:drawable="@drawable/quickactions_arrowup_middle_holo_light" />
+    <!-- TODO: provide callout-less state -->
+    <item android:drawable="@drawable/quickactions_arrowup_middle_holo_light" />
+</selector>
diff --git a/res/drawable/quickactions_arrow_right_holo_light.xml b/res/drawable/quickactions_arrow_right_holo_light.xml
new file mode 100644
index 0000000..3e309fe
--- /dev/null
+++ b/res/drawable/quickactions_arrow_right_holo_light.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_first="true" android:drawable="@drawable/quickactions_arrowdown_right_holo_light" />
+    <item android:state_last="true" android:drawable="@drawable/quickactions_arrowup_right_holo_light" />
+    <!-- TODO: provide callout-less state -->
+    <item android:drawable="@drawable/quickactions_arrowup_right_holo_light" />
+</selector>
diff --git a/res/layout/quickcontact.xml b/res/layout/quickcontact.xml
index a74424c..e2b291c 100644
--- a/res/layout/quickcontact.xml
+++ b/res/layout/quickcontact.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
 <!-- Copyright (C) 2009 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,13 +13,12 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
-<view
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    class="com.android.contacts.quickcontact.QuickContactRootLayout"
-    android:id="@+id/root"
-    android:layout_width="match_parent"
+    android:id="@android:id/content"
+    android:layout_width="@dimen/quick_contact_width"
     android:layout_height="wrap_content"
+    android:visibility="invisible"
     android:orientation="vertical">
 
     <FrameLayout
@@ -133,4 +133,4 @@
                 android:text="@string/quickcontact_clear_defaults_button" />
         </LinearLayout>
     </FrameLayout>
-</view>
+</LinearLayout>
diff --git a/res/layout/quickcontact_activity.xml b/res/layout/quickcontact_activity.xml
new file mode 100644
index 0000000..aced4a8
--- /dev/null
+++ b/res/layout/quickcontact_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.
+-->
+<view
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    class="com.android.contacts.quickcontact.FloatingChildLayout"
+    android:id="@+id/floating_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusable="true"
+    android:focusableInTouchMode="true"
+    android:descendantFocusability="afterDescendants">
+
+    <include layout="@layout/quickcontact" />
+
+</view>
diff --git a/res/layout/quickcontact_default_item.xml b/res/layout/quickcontact_default_item.xml
index 25b6910..3a918f0 100755
--- a/res/layout/quickcontact_default_item.xml
+++ b/res/layout/quickcontact_default_item.xml
@@ -28,13 +28,13 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:textStyle="bold"
-        android:textAppearance="?android:attr/textAppearanceMediumInverse" />
+        android:textAppearance="?android:attr/textAppearanceMedium" />
 
     <TextView
         android:id="@android:id/text2"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="-4dip"
-        android:textAppearance="?android:attr/textAppearanceSmallInverse" />
+        android:textAppearance="?android:attr/textAppearanceSmall" />
 
 </LinearLayout>
diff --git a/res/layout/quickcontact_resolve_item.xml b/res/layout/quickcontact_resolve_item.xml
index 55de80e..2805722 100755
--- a/res/layout/quickcontact_resolve_item.xml
+++ b/res/layout/quickcontact_resolve_item.xml
@@ -28,13 +28,13 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:textStyle="bold"
-        android:textAppearance="?android:attr/textAppearanceMediumInverse" />
+        android:textAppearance="?android:attr/textAppearanceMedium" />
 
     <TextView
         android:id="@android:id/text2"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginTop="-4dip"
-        android:textAppearance="?android:attr/textAppearanceSmallInverse" />
+        android:textAppearance="?android:attr/textAppearanceSmall" />
 
 </LinearLayout>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index b8e9874..c776e1d 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -38,38 +38,19 @@
         <item name="android:windowIsFloating">true</item>
     </style>
 
-    <style name="FullyTranslucent" parent="android:Theme.Translucent.NoTitleBar">
-        <item name="android:windowContentOverlay">@null</item>
+    <style name="Theme">
     </style>
 
-    <style name="FullyTranslucent.QuickContact">
-        <!-- This is a hack because we want to be able to animate away the
-             QuickContact window, and we close its containing activity at the
-             same time.  So put in a dummy animation so this guy sticks around
-             while the fast track window is animating. -->
-        <item name="android:windowAnimationStyle">@style/DummyAnimation</item>
-    </style>
-
-    <style name="QuickContact" parent="@android:Theme.Holo.Light">
-        <item name="android:windowNoTitle">true</item>
-        <item name="android:windowFrame">@null</item>
+    <style name="Theme.QuickContact" parent="@android:style/Theme.Holo.Light">
         <item name="android:windowBackground">@android:color/transparent</item>
-        <item name="android:windowIsFloating">true</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:windowFrame">@null</item>
         <item name="android:windowContentOverlay">@null</item>
-        <!-- TODO: create our own animation style in framework -->
-        <!--
-        <item name="android:windowAnimationStyle">@*android:style/Animation.ZoomButtons</item>
-        -->
-    </style>
-
-    <style name="QuickContactAboveAnimation">
-        <item name="android:windowEnterAnimation">@anim/quickcontact_above_enter</item>
-        <item name="android:windowExitAnimation">@anim/quickcontact_above_exit</item>
-    </style>
-
-    <style name="QuickContactBelowAnimation">
-        <item name="android:windowEnterAnimation">@anim/quickcontact_below_enter</item>
-        <item name="android:windowExitAnimation">@anim/quickcontact_below_exit</item>
+        <item name="android:windowAnimationStyle">@null</item>
+        <item name="android:windowIsFloating">false</item>
+        <item name="android:backgroundDimEnabled">false</item>
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowNoTitle">true</item>
     </style>
 
     <style name="ContactsSearchAnimation">
diff --git a/src/com/android/contacts/quickcontact/FloatingChildLayout.java b/src/com/android/contacts/quickcontact/FloatingChildLayout.java
new file mode 100644
index 0000000..ddba609
--- /dev/null
+++ b/src/com/android/contacts/quickcontact/FloatingChildLayout.java
@@ -0,0 +1,231 @@
+/*
+ * 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.quickcontact;
+
+import com.android.contacts.R;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.AnimationUtils;
+import android.widget.FrameLayout;
+import android.widget.PopupWindow;
+
+/**
+ * Layout containing single child {@link View} which it attempts to center
+ * around {@link #setChildTargetScreen(Rect)}.
+ * <p>
+ * Updates drawable state to be {@link android.R.attr#state_first} when child is
+ * above target, and {@link android.R.attr#state_last} when child is below
+ * target. Also updates {@link Drawable#setLevel(int)} on child
+ * {@link View#getBackground()} to reflect horizontal center of target.
+ * <p>
+ * The reason for this approach is because target {@link Rect} is in screen
+ * coordinates disregarding decor insets; otherwise something like
+ * {@link PopupWindow} might work better.
+ */
+public class FloatingChildLayout extends FrameLayout {
+    private static final String TAG = "FloatingChild";
+
+    public FloatingChildLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    private View mChild;
+
+    private Rect mTargetScreen = new Rect();
+
+    private int mCalloutState = 0;
+    private int mCalloutLeft;
+
+    @Override
+    protected void onFinishInflate() {
+        mChild = findViewById(android.R.id.content);
+        mChild.setDuplicateParentStateEnabled(true);
+    }
+
+    public View getChild() {
+        return mChild;
+    }
+
+    /**
+     * Set {@link Rect} in screen coordinates that {@link #getChild()} should be
+     * centered around.
+     */
+    public void setChildTargetScreen(Rect targetScreen) {
+        mTargetScreen = targetScreen;
+        requestLayout();
+    }
+
+    /**
+     * Return {@link #mTargetScreen} in local window coordinates, taking any
+     * decor insets into account.
+     */
+    private Rect getTargetInWindow() {
+        final Rect windowScreen = new Rect();
+        getWindowVisibleDisplayFrame(windowScreen);
+
+        final Rect target = new Rect(mTargetScreen);
+        target.offset(-windowScreen.left, -windowScreen.top);
+        return target;
+    }
+
+    private void updateCallout(int calloutState, int calloutLeft) {
+        if (mCalloutState != calloutState) {
+            mCalloutState = calloutState;
+            mChild.refreshDrawableState();
+        }
+
+        final Drawable background = mChild.getBackground();
+        if (background != null && mCalloutLeft != calloutLeft) {
+            mCalloutLeft = calloutLeft;
+            background.setLevel(calloutLeft);
+        }
+    }
+
+    @Override
+    protected int[] onCreateDrawableState(int extraSpace) {
+        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+        mergeDrawableStates(drawableState, new int[] { mCalloutState });
+        return drawableState;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+
+        final View child = mChild;
+        final Rect target = getTargetInWindow();
+
+        final int childWidth = child.getMeasuredWidth();
+        final int childHeight = child.getMeasuredHeight();
+
+        // default is no callout, left-aligned, and vertically centered
+        int calloutState = 0;
+        int childLeft = target.left;
+        int childTop = target.centerY() - (childHeight / 2);
+
+        // when target is wide, horizontally center instead of left-align
+        if (target.width() > childWidth / 2) {
+            childLeft = target.centerX() - (childWidth / 2);
+        }
+
+        final int areaAboveTarget = target.top;
+        final int areaBelowTarget = getHeight() - target.bottom;
+
+        if (areaAboveTarget >= childHeight) {
+            // enough room above target, place above and callout down
+            calloutState = android.R.attr.state_first;
+            childTop = target.top - childHeight;
+
+        } else if (areaBelowTarget >= childHeight) {
+            // enough room below target, place below and callout up
+            calloutState = android.R.attr.state_last;
+            childTop = target.bottom;
+        }
+
+        // when child is outside bounds, nudge back inside
+        childLeft = clampDimension(childLeft, childWidth, getWidth());
+        childTop = clampDimension(childTop, childHeight, getHeight());
+
+        final int calloutLeft = target.centerX() - childLeft;
+        updateCallout(calloutState, calloutLeft);
+        layoutChild(child, childLeft, childTop);
+
+    }
+
+    private static int clampDimension(int value, int size, int max) {
+        // when larger than bounds, just center
+        if (size > max) {
+            return (max - size) / 2;
+        }
+
+        // clamp to lower bound
+        value = Math.max(value, 0);
+        // clamp to higher bound
+        value = Math.min(value, max - size);
+
+        return value;
+    }
+
+    private static void layoutChild(View child, int left, int top) {
+        child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
+    }
+
+    /**
+     * Begin animating {@link #getChild()} visible.
+     */
+    public void showChild() {
+        final boolean calloutAbove = mCalloutState == android.R.attr.state_first;
+        final Animation anim = AnimationUtils.loadAnimation(getContext(),
+                calloutAbove ? R.anim.quickcontact_above_enter : R.anim.quickcontact_below_enter);
+        mChild.startAnimation(anim);
+        mChild.setVisibility(View.VISIBLE);
+    }
+
+    /**
+     * Begin animating {@link #getChild()} invisible.
+     */
+    public void hideChild(final Runnable onAnimationEnd) {
+        final boolean calloutAbove = mCalloutState == android.R.attr.state_first;
+        final Animation anim = AnimationUtils.loadAnimation(getContext(),
+                calloutAbove ? R.anim.quickcontact_above_exit : R.anim.quickcontact_below_exit);
+
+        if (onAnimationEnd != null) {
+            anim.setAnimationListener(new AnimationListener() {
+                /** {@inheritDoc} */
+                public void onAnimationStart(Animation animation) {
+                    // ignored
+                }
+
+                /** {@inheritDoc} */
+                public void onAnimationRepeat(Animation animation) {
+                    // ignored
+                }
+
+                /** {@inheritDoc} */
+                public void onAnimationEnd(Animation animation) {
+                    onAnimationEnd.run();
+                }
+            });
+        }
+
+        mChild.startAnimation(anim);
+        mChild.setVisibility(View.INVISIBLE);
+    }
+
+    private View.OnTouchListener mOutsideTouchListener;
+
+    public void setOnOutsideTouchListener(View.OnTouchListener listener) {
+        mOutsideTouchListener = listener;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        // at this point, touch wasn't handled by child view; assume outside
+        if (mOutsideTouchListener != null) {
+            return mOutsideTouchListener.onTouch(this, event);
+        } else {
+            return false;
+        }
+    }
+}
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
deleted file mode 100644
index 5032386..0000000
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2009 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.quickcontact;
-
-import com.android.contacts.ContactsActivity;
-
-import android.content.ContentUris;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.ContactsContract.QuickContact;
-import android.provider.ContactsContract.RawContacts;
-import android.util.Log;
-
-/**
- * Stub translucent activity that just shows {@link QuickContactWindow} floating
- * above the caller. This temporary hack should eventually be replaced with
- * direct framework support.
- */
-public final class QuickContactActivity extends ContactsActivity
-        implements QuickContactWindow.OnDismissListener {
-    private static final String TAG = "QuickContactActivity";
-
-    static final boolean LOGV = false;
-    static final boolean FORCE_CREATE = true;
-
-    private QuickContactWindow mQuickContact;
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        if (LOGV) Log.d(TAG, "onCreate");
-
-        this.onNewIntent(getIntent());
-    }
-
-    @Override
-    public void onNewIntent(Intent intent) {
-        super.onNewIntent(intent);
-        if (LOGV) Log.d(TAG, "onNewIntent");
-
-        if (QuickContactWindow.TRACE_LAUNCH) {
-            android.os.Debug.startMethodTracing(QuickContactWindow.TRACE_TAG);
-        }
-
-        if (mQuickContact == null || FORCE_CREATE) {
-            if (LOGV) Log.d(TAG, "Preparing window");
-            mQuickContact = new QuickContactWindow(this, this);
-        }
-
-        // Use our local window token for now
-        Uri lookupUri = intent.getData();
-        // Check to see whether it comes from the old version.
-        if (android.provider.Contacts.AUTHORITY.equals(lookupUri.getAuthority())) {
-            final long rawContactId = ContentUris.parseId(lookupUri);
-            lookupUri = RawContacts.getContactLookupUri(getContentResolver(),
-                    ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
-        }
-        final Bundle extras = intent.getExtras();
-
-        // Read requested parameters for displaying
-        final Rect target = intent.getSourceBounds();
-        final int mode = extras.getInt(QuickContact.EXTRA_MODE, QuickContact.MODE_MEDIUM);
-        final String[] excludeMimes = extras.getStringArray(QuickContact.EXTRA_EXCLUDE_MIMES);
-
-        mQuickContact.show(lookupUri, target, mode, excludeMimes);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void onBackPressed() {
-        if (LOGV) Log.w(TAG, "Unexpected back captured by stub activity");
-        finish();
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        if (LOGV) Log.d(TAG, "onPause");
-
-        // Dismiss any dialog when pausing
-        mQuickContact.dismiss();
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        if (LOGV) Log.d(TAG, "onDestroy");
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void onDismiss(QuickContactWindow dialog) {
-        if (LOGV) Log.d(TAG, "onDismiss");
-
-        if (isTaskRoot() && !FORCE_CREATE) {
-            // Instead of stopping, simply push this to the back of the stack.
-            // This is only done when running at the top of the stack;
-            // otherwise, we have been launched by someone else so need to
-            // allow the user to go back to the caller.
-            moveTaskToBack(false);
-        } else {
-            finish();
-        }
-    }
-}
diff --git a/src/com/android/contacts/quickcontact/QuickContactBackgroundDrawable.java b/src/com/android/contacts/quickcontact/QuickContactBackgroundDrawable.java
index 9118480..15311f9 100644
--- a/src/com/android/contacts/quickcontact/QuickContactBackgroundDrawable.java
+++ b/src/com/android/contacts/quickcontact/QuickContactBackgroundDrawable.java
@@ -26,37 +26,43 @@
 import android.graphics.drawable.Drawable;
 
 /**
- * Drawable that draws three pictures for the QuickContact-Background. ColorFilter is ignored
+ * Background {@link Drawable} for {@link QuickContactWindow} that draws arrow
+ * centered around requested position.
  */
 public class QuickContactBackgroundDrawable extends Drawable {
     private Drawable mLeftDrawable;
     private Drawable mMiddleDrawable;
     private Drawable mRightDrawable;
-    private int mRequestedX = Integer.MIN_VALUE;
-    private boolean mBoundsSet = false;
-    private int mAlpha = -1;
+
     private int mBottomOverride = Integer.MIN_VALUE;
 
+    public QuickContactBackgroundDrawable(Resources res) {
+        mLeftDrawable = res.getDrawable(R.drawable.quickactions_arrow_left_holo_light);
+        mMiddleDrawable = res.getDrawable(R.drawable.quickactions_arrow_middle_holo_light);
+        mRightDrawable = res.getDrawable(R.drawable.quickactions_arrow_right_holo_light);
+    }
+
     @Override
     public void setAlpha(int alpha) {
-        mAlpha = alpha;
-        setChildAlpha();
+        mLeftDrawable.setAlpha(alpha);
+        mMiddleDrawable.setAlpha(alpha);
+        mRightDrawable.setAlpha(alpha);
     }
 
     /**
-     * Overrides the bottom bounds. This is used for the animation when the QuickContact
-     * expands/collapses options
+     * Overrides the bottom bounds. This is used for the animation when the
+     * QuickContact expands/collapses options
      */
     public void setBottomOverride(int value) {
         mBottomOverride = value;
-        setChildBounds();
+        onBoundsChange(getBounds());
         invalidateSelf();
     }
 
     public void clearBottomOverride() {
         mBottomOverride = Integer.MIN_VALUE;
+        onBoundsChange(getBounds());
         invalidateSelf();
-        setChildBounds();
     }
 
     public float getBottomOverride() {
@@ -64,7 +70,29 @@
     }
 
     @Override
+    public boolean isStateful() {
+        return true;
+    }
+
+    @Override
+    protected boolean onStateChange(int[] state) {
+        super.onStateChange(state);
+        mLeftDrawable.setState(state);
+        mMiddleDrawable.setState(state);
+        mRightDrawable.setState(state);
+        return true;
+    }
+
+    @Override
+    protected boolean onLevelChange(int level) {
+        return true;
+    }
+
+    @Override
     public void setColorFilter(ColorFilter cf) {
+        mLeftDrawable.setColorFilter(cf);
+        mMiddleDrawable.setColorFilter(cf);
+        mRightDrawable.setColorFilter(cf);
     }
 
     @Override
@@ -72,54 +100,22 @@
         return PixelFormat.TRANSLUCENT;
     }
 
-    public void configure(Resources resources, boolean arrowUp, int requestedX) {
-        mLeftDrawable = resources.getDrawable(arrowUp
-                ? R.drawable.quickactions_arrowup_left_holo_light
-                : R.drawable.quickactions_arrowdown_left_holo_light);
-        mMiddleDrawable = resources.getDrawable(arrowUp
-                ? R.drawable.quickactions_arrowup_middle_holo_light
-                : R.drawable.quickactions_arrowdown_middle_holo_light);
-        mRightDrawable = resources.getDrawable(arrowUp
-                ? R.drawable.quickactions_arrowup_right_holo_light
-                : R.drawable.quickactions_arrowdown_right_holo_light);
-
-        mRequestedX = requestedX;
-
-        setChildAlpha();
-        setChildBounds();
-    }
-
     @Override
     protected void onBoundsChange(Rect bounds) {
-        mBoundsSet = true;
-        setChildBounds();
-    }
+        final int requestedX = getLevel();
 
-    private void setChildAlpha() {
-        if (mAlpha == -1) return;
-
-        if (mLeftDrawable != null) mLeftDrawable.setAlpha(mAlpha);
-        if (mMiddleDrawable != null) mMiddleDrawable.setAlpha(mAlpha);
-        if (mRightDrawable != null) mRightDrawable.setAlpha(mAlpha);
-    }
-
-    private void setChildBounds() {
-        if (mRequestedX == Integer.MIN_VALUE) return;
-        if (!mBoundsSet) return;
-
-        final Rect bounds = getBounds();
-        int middleLeft = mRequestedX - mMiddleDrawable.getIntrinsicWidth() / 2;
-        int middleRight = mRequestedX + mMiddleDrawable.getIntrinsicWidth() / 2;
+        int middleLeft = requestedX - mMiddleDrawable.getIntrinsicWidth() / 2;
+        int middleRight = requestedX + mMiddleDrawable.getIntrinsicWidth() / 2;
 
         // ensure left drawable is not smaller than its Intrinsic Width
-        final int leftExtra =  (middleLeft - bounds.left) - mLeftDrawable.getIntrinsicWidth();
+        final int leftExtra = (middleLeft - bounds.left) - mLeftDrawable.getIntrinsicWidth();
         if (leftExtra < 0) {
             middleLeft -= leftExtra;
             middleRight -= leftExtra;
         }
 
         // ensure right drawable is not smaller than its Intrinsic Width
-        final int rightExtra =  (bounds.right - middleRight) - mRightDrawable.getIntrinsicWidth();
+        final int rightExtra = (bounds.right - middleRight) - mRightDrawable.getIntrinsicWidth();
         if (rightExtra < 0) {
             middleLeft += rightExtra;
             middleRight += rightExtra;
diff --git a/src/com/android/contacts/quickcontact/QuickContactRootLayout.java b/src/com/android/contacts/quickcontact/QuickContactRootLayout.java
deleted file mode 100644
index 007783a..0000000
--- a/src/com/android/contacts/quickcontact/QuickContactRootLayout.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.contacts.quickcontact;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.widget.LinearLayout;
-
-/**
- * Custom layout for Quick Contact. It intercepts the BACK key and
- * close QC even when the soft keyboard is open.
- */
-public class QuickContactRootLayout extends LinearLayout {
-    private Listener mListener;
-
-    public QuickContactRootLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public void setListener(Listener value) {
-        mListener = value;
-    }
-
-    /**
-     * Intercepts the BACK key event and dismisses QuickContact window.
-     */
-    @Override
-    public boolean dispatchKeyEventPreIme(KeyEvent event) {
-        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
-            if (mListener != null) mListener.onBackPressed();
-            return true;
-        }
-        return super.dispatchKeyEventPreIme(event);
-    }
-
-    public interface Listener {
-        void onBackPressed();
-    }
-}
diff --git a/src/com/android/contacts/quickcontact/QuickContactWindow.java b/src/com/android/contacts/quickcontact/QuickContactWindow.java
index 2a6dd2f..5f4bcc9 100644
--- a/src/com/android/contacts/quickcontact/QuickContactWindow.java
+++ b/src/com/android/contacts/quickcontact/QuickContactWindow.java
@@ -25,13 +25,15 @@
 import com.android.contacts.util.Constants;
 import com.android.contacts.util.DataStatus;
 import com.android.contacts.util.NotifyingAsyncQueryHandler;
-import com.android.internal.policy.PolicyManager;
 import com.google.android.collect.Lists;
+import com.google.common.base.Preconditions;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
+import android.app.Activity;
 import android.content.ActivityNotFoundException;
+import android.content.ContentUris;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -41,6 +43,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Im;
@@ -54,30 +57,16 @@
 import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsContract.RawContacts;
 import android.text.TextUtils;
-import android.util.Log;
-import android.view.ActionMode;
-import android.view.ContextThemeWrapper;
-import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.view.ViewStub;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.view.Window;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
 import android.widget.AbsListView;
 import android.widget.AdapterView;
 import android.widget.BaseAdapter;
 import android.widget.Button;
 import android.widget.CheckBox;
 import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ListView;
@@ -88,72 +77,44 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
 
 /**
- * Window that shows QuickContact dialog for a specific {@link Contacts#_ID}.
+ * Mostly translucent {@link Activity} that shows QuickContact dialog. It loads
+ * data asynchronously, and then shows a popup with details centered around
+ * {@link Intent#getSourceBounds()}.
  */
-public class QuickContactWindow implements Window.Callback,
+public class QuickContactWindow extends Activity implements
         NotifyingAsyncQueryHandler.AsyncQueryListener, View.OnClickListener,
-        AbsListView.OnItemClickListener, KeyEvent.Callback, OnGlobalLayoutListener,
-        QuickContactRootLayout.Listener {
+        AbsListView.OnItemClickListener {
+    private static final String TAG = "QuickContact";
 
-    private static final String TAG = "QuickContactWindow";
+    private static final boolean TRACE_LAUNCH = false;
+    private static final String TRACE_TAG = "quickcontact";
 
-    /**
-     * Interface used to allow the person showing a {@link QuickContactWindow} to
-     * know when the window has been dismissed.
-     */
-    public interface OnDismissListener {
-        public void onDismiss(QuickContactWindow dialog);
-    }
-
-    private final static int ANIMATION_FADE_IN_TIME = 100;
-    private final static int ANIMATION_FADE_OUT_TIME = 100;
-    private final static int ANIMATION_EXPAND_TIME = 100;
-    private final static int ANIMATION_COLLAPSE_TIME = 100;
-
-    /**
-     * If the anchor is wider than (quick contact width * this constant) then
-     * center quick contact.  Otherwise, left-align.
-     */
-    private static final double MIN_RELATIVE_ANCHOR_WIDTH_TO_CENTER = 0.5;
-
-    private final Context mContext;
-    private final LayoutInflater mInflater;
-    private final WindowManager mWindowManager;
-    private Window mWindow;
-    private View mDecor;
-    private final Rect mRect = new Rect();
-
-    private boolean mDismissed = false;
-    private boolean mQuerying = false;
-    private boolean mShowing = false;
+    private static final int ANIMATION_FADE_IN_TIME = 100;
+    private static final int ANIMATION_FADE_OUT_TIME = 100;
+    private static final int ANIMATION_EXPAND_TIME = 100;
+    private static final int ANIMATION_COLLAPSE_TIME = 100;
 
     private NotifyingAsyncQueryHandler mHandler;
-    private OnDismissListener mDismissListener;
 
     private Uri mLookupUri;
-    private Rect mAnchor;
-
-    private int mScreenWidth;
-    private int mUseableScreenHeight;
-    private int mRequestedY;
+    private int mMode;
+    private String[] mExcludeMimes;
 
     private boolean mHasValidSocial = false;
 
-    private int mMode;
-    private QuickContactRootLayout mRootView;
+    private FloatingChildLayout mFloatingLayout;
     private QuickContactBackgroundDrawable mBackground;
-    private View mHeader;
-    private HorizontalScrollView mTrackScroll;
-    private ViewGroup mTrack;
 
+    private View mHeader;
+    private ViewGroup mTrack;
     private FrameLayout mFooter;
     private LinearLayout mFooterDisambig;
     private LinearLayout mFooterClearDefaults;
+
     private ListView mResolveList;
     private CheckableImageView mLastAction;
     private CheckBox mSetPrimaryCheckBox;
@@ -165,9 +126,6 @@
      */
     private HashMap<String, Action> mDefaultsMap = new HashMap<String, Action>();
 
-    private int mWindowRecycled = 0;
-    private int mActionRecycled = 0;
-
     /**
      * Set of {@link Action} that are associated with the aggregate currently
      * displayed by this dialog, represented as a map from {@link String}
@@ -176,14 +134,6 @@
     private ActionMultiMap mActions = new ActionMultiMap();
 
     /**
-     * Pool of unused {@link CheckableImageView} that have previously been
-     * inflated, and are ready to be recycled through {@link #obtainView()}.
-     */
-    private LinkedList<CheckableImageView> mActionPool = new LinkedList<CheckableImageView>();
-
-    private String[] mExcludeMimes;
-
-    /**
      * {@link #PRECEDING_MIMETYPES} and {@link #FOLLOWING_MIMETYPES} are used to sort MIME-types.
      *
      * <p>The MIME-types in {@link #PRECEDING_MIMETYPES} appear in the front of the dialog,
@@ -221,82 +171,48 @@
     });
     private static final int TOKEN_DATA = 1;
 
-    static final boolean TRACE_LAUNCH = false;
-    static final String TRACE_TAG = "quickcontact";
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
 
-    /**
-     * Prepare a dialog to show in the given {@link Context}.
-     */
-    public QuickContactWindow(Context context) {
-        mContext = new ContextThemeWrapper(context, R.style.QuickContact);
-        mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+        setContentView(R.layout.quickcontact_activity);
 
-        mWindow = PolicyManager.makeNewWindow(mContext);
-        mWindow.setCallback(this);
-        mWindow.setWindowManager(mWindowManager, null, null);
-        mWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED);
+        mBackground = new QuickContactBackgroundDrawable(getResources());
 
-        mWindow.setContentView(R.layout.quickcontact);
+        mFloatingLayout = findTypedViewById(R.id.floating_layout);
+        mFloatingLayout.getChild().setBackgroundDrawable(mBackground);
+        mFloatingLayout.setOnOutsideTouchListener(mOnOutsideTouchListener);
 
-        mRootView = (QuickContactRootLayout)mWindow.findViewById(R.id.root);
-        mRootView.setListener(this);
-        mRootView.setFocusable(true);
-        mRootView.setFocusableInTouchMode(true);
-        mRootView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+        mTrack = findTypedViewById(R.id.quickcontact);
+        mFooter = findTypedViewById(R.id.footer);
+        mFooterDisambig = findTypedViewById(R.id.footer_disambig);
+        mFooterClearDefaults = findTypedViewById(R.id.footer_clear_defaults);
+        mResolveList = findTypedViewById(android.R.id.list);
+        mSetPrimaryCheckBox = findTypedViewById(android.R.id.checkbox);
 
-        mBackground = new QuickContactBackgroundDrawable();
-        mRootView.setBackgroundDrawable(mBackground);
+        mDefaultsListView = findTypedViewById(R.id.defaults_list);
 
-        mScreenWidth = mWindowManager.getDefaultDisplay().getWidth();
-        // Status bar height
-        final int screenMarginBottom = context.getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.screen_margin_bottom);
-        mUseableScreenHeight = mWindowManager.getDefaultDisplay().getHeight() - screenMarginBottom;
+        mClearDefaultsButton = findTypedViewById(R.id.clear_defaults_button);
+        mClearDefaultsButton.setOnClickListener(mOnClearDefaultsClickListener);
 
-        mTrack = (ViewGroup) mWindow.findViewById(R.id.quickcontact);
-        mTrackScroll = (HorizontalScrollView) mWindow.findViewById(R.id.scroll);
+        mResolveList.setOnItemClickListener(this);
 
-        mFooter = (FrameLayout) mWindow.findViewById(R.id.footer);
-        mFooterDisambig = (LinearLayout) mWindow.findViewById(R.id.footer_disambig);
-        mFooterClearDefaults = (LinearLayout) mWindow.findViewById(R.id.footer_clear_defaults);
-        mResolveList = (ListView) mWindow.findViewById(android.R.id.list);
-        mSetPrimaryCheckBox = (CheckBox) mWindow.findViewById(android.R.id.checkbox);
+        mHandler = new NotifyingAsyncQueryHandler(this, this);
 
-        mDefaultsListView = (ListView) mWindow.findViewById(R.id.defaults_list);
-        mClearDefaultsButton = (Button) mWindow.findViewById(R.id.clear_defaults_button);
-        mClearDefaultsButton.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                clearDefaults();
-            }
-        });
-
-        mResolveList.setOnItemClickListener(QuickContactWindow.this);
-
-        mHandler = new NotifyingAsyncQueryHandler(mContext, this);
-    }
-
-    /**
-     * Prepare a dialog to show in the given {@link Context}, and notify the
-     * given {@link OnDismissListener} each time this dialog is dismissed.
-     */
-    public QuickContactWindow(Context context, OnDismissListener dismissListener) {
-        this(context);
-        mDismissListener = dismissListener;
+        show();
     }
 
     private View getHeaderView(int mode) {
         View header = null;
         switch (mode) {
             case QuickContact.MODE_SMALL:
-                header = mWindow.findViewById(R.id.header_small);
+                header = findViewById(R.id.header_small);
                 break;
             case QuickContact.MODE_MEDIUM:
-                header = mWindow.findViewById(R.id.header_medium);
+                header = findViewById(R.id.header_medium);
                 break;
             case QuickContact.MODE_LARGE:
-                header = mWindow.findViewById(R.id.header_large);
+                header = findViewById(R.id.header_large);
                 break;
         }
 
@@ -311,60 +227,52 @@
         return header;
     }
 
-    /**
-     * Start showing a dialog for the given {@link Contacts#_ID} pointing
-     * towards the given location.
-     */
-    public synchronized void show(Uri lookupUri, Rect anchor, int mode, String[] excludeMimes) {
-        if (mQuerying || mShowing) {
-            Log.w(TAG, "dismissing before showing");
-            dismissInternal();
-        }
+    private void show() {
 
-        if (TRACE_LAUNCH && !android.os.Debug.isMethodTracingActive()) {
+        if (TRACE_LAUNCH) {
             android.os.Debug.startMethodTracing(TRACE_TAG);
         }
 
-        // Validate incoming parameters
-        final boolean validMode = (mode == QuickContact.MODE_SMALL
-                || mode == QuickContact.MODE_MEDIUM || mode == QuickContact.MODE_LARGE);
-        if (!validMode) {
-            throw new IllegalArgumentException("Invalid mode, expecting MODE_LARGE, "
-                    + "MODE_MEDIUM, or MODE_SMALL");
+        final Intent intent = getIntent();
+
+        Uri lookupUri = intent.getData();
+
+        // Check to see whether it comes from the old version.
+        if (android.provider.Contacts.AUTHORITY.equals(lookupUri.getAuthority())) {
+            final long rawContactId = ContentUris.parseId(lookupUri);
+            lookupUri = RawContacts.getContactLookupUri(getContentResolver(),
+                    ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
         }
 
-        if (anchor == null) {
-            throw new IllegalArgumentException("Missing anchor rectangle");
+        mLookupUri = Preconditions.checkNotNull(lookupUri, "missing lookupUri");
+
+        // Read requested parameters for displaying
+        final Rect targetScreen = intent.getSourceBounds();
+        Preconditions.checkNotNull(targetScreen, "missing targetScreen");
+        mFloatingLayout.setChildTargetScreen(targetScreen);
+
+        mMode = intent.getIntExtra(QuickContact.EXTRA_MODE, QuickContact.MODE_MEDIUM);
+        mExcludeMimes = intent.getStringArrayExtra(QuickContact.EXTRA_EXCLUDE_MIMES);
+
+        switch (mMode) {
+            case QuickContact.MODE_SMALL:
+            case QuickContact.MODE_MEDIUM:
+            case QuickContact.MODE_LARGE:
+                break;
+            default:
+                throw new IllegalArgumentException("Unexpected mode: " + mMode);
         }
 
-        // Prepare header view for requested mode
-        mLookupUri = lookupUri;
-        mAnchor = new Rect(anchor);
-        mMode = mode;
-        mExcludeMimes = excludeMimes;
-
-        mHeader = getHeaderView(mode);
-
+        // find and prepare correct header view
+        mHeader = getHeaderView(mMode);
         setHeaderText(R.id.name, R.string.quickcontact_missing_name);
-
         setHeaderText(R.id.status, null);
         setHeaderText(R.id.timestamp, null);
-
         setHeaderImage(R.id.presence, null);
 
-        resetTrack();
-
-        // We need to have a focused view inside the QuickContact window so
-        // that the BACK key event can be intercepted
-        mRootView.requestFocus();
-
-        mHasValidSocial = false;
-        mDismissed = false;
-        mQuerying = true;
-
         // Start background query for data, but only select photo rows when they
         // directly match the super-primary PHOTO_ID.
-        final Uri dataUri = getDataUri(lookupUri);
+        final Uri dataUri = Uri.withAppendedPath(lookupUri, Contacts.Data.CONTENT_DIRECTORY);
         mHandler.cancelOperation(TOKEN_DATA);
 
         // Only request photo data when required by mode
@@ -380,226 +288,85 @@
         }
     }
 
-    /**
-     * Build a {@link Uri} into the {@link Data} table for the requested
-     * {@link Contacts#CONTENT_LOOKUP_URI} style {@link Uri}.
-     */
-    private Uri getDataUri(Uri lookupUri) {
-        return Uri.withAppendedPath(lookupUri, Contacts.Data.CONTENT_DIRECTORY);
+    @SuppressWarnings("unchecked")
+    private <T> T findTypedViewById(int id) {
+        return (T) super.findViewById(id);
     }
 
-    /**
-     * Creates and configures the background resource
-     */
-    private void configureBackground(boolean arrowUp, int requestedX) {
-        mBackground.configure(mContext.getResources(), arrowUp, requestedX);
+    private View.OnTouchListener mOnOutsideTouchListener = new View.OnTouchListener() {
+        /** {@inheritDoc} */
+        public boolean onTouch(View v, MotionEvent event) {
+            hide(true);
+            return true;
+        }
+    };
+
+    private View.OnClickListener mOnClearDefaultsClickListener = new View.OnClickListener() {
+        /** {@inheritDoc} */
+        public void onClick(View v) {
+            clearDefaults();
+        }
+    };
+
+    private void hide(boolean withAnimation) {
+        // cancel any pending queries
+        mHandler.cancelOperation(TOKEN_DATA);
+
+        if (withAnimation) {
+            mFloatingLayout.hideChild(new Runnable() {
+                /** {@inheritDoc} */
+                public void run() {
+                    finish();
+                }
+            });
+        } else {
+            mFloatingLayout.hideChild(null);
+            finish();
+        }
     }
 
-    /**
-     * Actual internal method to show this dialog. Called only by
-     * {@link #considerShowing()} when all data requirements have been met.
-     */
-    private void showInternal() {
-        mDecor = mWindow.getDecorView();
-        mDecor.getViewTreeObserver().addOnGlobalLayoutListener(this);
-        WindowManager.LayoutParams layoutParams = mWindow.getAttributes();
-
-        layoutParams.width = mContext.getResources().getDimensionPixelSize(
-                R.dimen.quick_contact_width);
-        layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
-
-        // Try to left align with the anchor control or center if the anchor is wide
-        if (mAnchor.left + layoutParams.width <= mScreenWidth) {
-            if (mAnchor.width() > layoutParams.width * MIN_RELATIVE_ANCHOR_WIDTH_TO_CENTER) {
-                layoutParams.x = mAnchor.left + (mAnchor.width() - layoutParams.width) / 2;
-            } else {
-                layoutParams.x = mAnchor.left;
-            }
-        } else {
-            // Not enough space. Try to right align to the anchor
-            if (mAnchor.right - layoutParams.width >= 0) {
-                layoutParams.x = mAnchor.right - layoutParams.width;
-            } else {
-                // Also not enough space. Use the whole screen width available
-                layoutParams.x = 0;
-                layoutParams.width = mScreenWidth;
-            }
-        }
-
-        // Force layout measuring pass so we have baseline numbers
-        mDecor.measure(layoutParams.width, layoutParams.height);
-        final int blockHeight = mDecor.getMeasuredHeight();
-
-        layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
-
-        if (mUseableScreenHeight - mAnchor.bottom > blockHeight) {
-            // Show downwards callout when enough room, aligning block top with bottom of
-            // anchor area, and adjusting to inset arrow.
-            configureBackground(true, mAnchor.centerX() - layoutParams.x);
-            layoutParams.y = mAnchor.bottom;
-            layoutParams.windowAnimations = R.style.QuickContactBelowAnimation;
-        } else {
-            // Show upwards callout, aligning bottom block
-            // edge with top of anchor area, and adjusting to inset arrow.
-            configureBackground(false, mAnchor.centerX() - layoutParams.x);
-            layoutParams.y = mAnchor.top - blockHeight;
-            layoutParams.windowAnimations = R.style.QuickContactAboveAnimation;
-        }
-
-        layoutParams.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
-
-        mRequestedY = layoutParams.y;
-        mWindowManager.addView(mDecor, layoutParams);
-        mShowing = true;
-        mQuerying = false;
-        mDismissed = false;
-
-        if (TRACE_LAUNCH) {
-            android.os.Debug.stopMethodTracing();
-            Log.d(TAG, "Window recycled " + mWindowRecycled + " times, chiclets "
-                    + mActionRecycled + " times");
-        }
+    @Override
+    public void onBackPressed() {
+        hide(true);
     }
 
     /** {@inheritDoc} */
-    @Override
-    public void onGlobalLayout() {
-        layoutInScreen();
-    }
-
-    /**
-     * Adjust vertical {@link WindowManager.LayoutParams} to fit window as best
-     * as possible, shifting up to display content as needed.
-     */
-    private void layoutInScreen() {
-        if (!mShowing) return;
-
-        final WindowManager.LayoutParams l = mWindow.getAttributes();
-        final int originalY = l.y;
-
-        final int blockHeight = mDecor.getHeight();
-
-        l.y = mRequestedY;
-        if (mRequestedY + blockHeight > mUseableScreenHeight) {
-            // Shift up from bottom when overflowing
-            l.y = mUseableScreenHeight - blockHeight;
-        }
-
-        if (originalY != l.y) {
-            // Only update when value is changed
-            mWindow.setAttributes(l);
-        }
-    }
-
-    /**
-     * Dismiss this dialog if showing.
-     */
-    public synchronized void dismiss() {
-        // Notify any listeners that we've been dismissed
-        if (mDismissListener != null) {
-            mDismissListener.onDismiss(this);
-        }
-
-        dismissInternal();
-    }
-
-    private void dismissInternal() {
-        // Remove any attached window decor for recycling
-        boolean hadDecor = mDecor != null;
-        if (hadDecor) {
-            mWindowManager.removeView(mDecor);
-            mWindowRecycled++;
-            mDecor.getViewTreeObserver().removeGlobalOnLayoutListener(this);
-            mDecor = null;
-            mWindow.closeAllPanels();
-        }
-        mShowing = false;
-        mDismissed = true;
-
-        // Cancel any pending queries
-        mHandler.cancelOperation(TOKEN_DATA);
-        mQuerying = false;
-
-        // Completely hide header and reset track
-        mHeader.setVisibility(View.GONE);
-        resetTrack();
-    }
-
-    /**
-     * Reset track to initial state, recycling any chiclets.
-     */
-    private void resetTrack() {
-        // Clear background height-animation override
-        mBackground.clearBottomOverride();
-
-        // Release reference to last chiclet
-        mLastAction = null;
-
-        // Clear track actions and scroll to hard left
-        mActions.clear();
-
-        // Recycle any chiclets in use
-        for (int i = mTrack.getChildCount() - 1; i >= 0; i--) {
-            final View child = mTrack.getChildAt(i);
-            // there can be non-CheckableImageView children, e.g. a "No Data" label
-            if (child instanceof CheckableImageView) {
-                releaseView((CheckableImageView)child);
+    public synchronized void onQueryComplete(int token, Object cookie, Cursor cursor) {
+        try {
+            if (isFinishing()) {
+                hide(false);
+                return;
+            } else if (cursor == null || cursor.getCount() == 0) {
+                Toast.makeText(this, R.string.invalidContactMessage, Toast.LENGTH_LONG).show();
+                hide(false);
+                return;
             }
-            mTrack.removeViewAt(i);
-        }
 
-        mTrackScroll.fullScroll(View.FOCUS_LEFT);
+            bindData(cursor);
 
-        // Clear any primary requests
-        mSetPrimaryCheckBox.setChecked(false);
-
-        setNewActionViewChecked(null);
-        mFooter.setVisibility(View.GONE);
-    }
-
-    /**
-     * Consider showing this window, which will only call through to
-     * {@link #showInternal()} when all data items are present.
-     */
-    private void considerShowing() {
-        if (!mShowing && !mDismissed) {
             if (mMode == QuickContact.MODE_MEDIUM && !mHasValidSocial) {
                 // Missing valid social, swap medium for small header
                 mHeader.setVisibility(View.GONE);
                 mHeader = getHeaderView(QuickContact.MODE_SMALL);
             }
 
-            // All queries have returned, pull curtain
-            showInternal();
+            if (TRACE_LAUNCH) {
+                android.os.Debug.stopMethodTracing();
+            }
+
+            // data bound and ready, pull curtain to show
+            mFloatingLayout.showChild();
+
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
         }
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public synchronized void onQueryComplete(int token, Object cookie, Cursor cursor) {
-        // Bail early when query is stale
-        if (cookie != mLookupUri) return;
-
-        if (cursor == null) {
-            // Problem while running query, so bail without showing
-            Log.w(TAG, "Missing cursor for token=" + token);
-            this.dismiss();
-            return;
-        }
-
-        handleData(cursor);
-
-        if (!cursor.isClosed()) {
-            cursor.close();
-        }
-
-        considerShowing();
-    }
-
     /** Assign this string to the view, if found in {@link #mHeader}. */
     private void setHeaderText(int id, int resId) {
-        setHeaderText(id, mContext.getResources().getText(resId));
+        setHeaderText(id, getText(resId));
     }
 
     /** Assign this string to the view, if found in {@link #mHeader}. */
@@ -637,26 +404,22 @@
     /**
      * Handle the result from the {@link #TOKEN_DATA} query.
      */
-    private void handleData(Cursor cursor) {
-        final ResolveCache cache = ResolveCache.getInstance(mContext);
-        if (cursor == null) return;
-        if (cursor.getCount() == 0) {
-            Toast.makeText(mContext, R.string.invalidContactMessage, Toast.LENGTH_LONG).show();
-            dismiss();
-            return;
-        }
+    private void bindData(Cursor cursor) {
+        final ResolveCache cache = ResolveCache.getInstance(this);
+        final Context context = this;
 
         if (!isMimeExcluded(Contacts.CONTENT_ITEM_TYPE)) {
             // Add the profile shortcut action
-            final Action action = new ProfileAction(mContext, mLookupUri);
+            final Action action = new ProfileAction(context, mLookupUri);
             mActions.put(Contacts.CONTENT_ITEM_TYPE, action);
         }
 
         mDefaultsMap.clear();
 
         final DataStatus status = new DataStatus();
-        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
-        final ImageView photoView = (ImageView)mHeader.findViewById(R.id.photo);
+        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(
+                context.getApplicationContext());
+        final ImageView photoView = (ImageView) mHeader.findViewById(R.id.photo);
 
         Bitmap photoBitmap = null;
         while (cursor.moveToNext()) {
@@ -688,7 +451,7 @@
                 // Build an action for this data entry, find a mapping to a UI
                 // element, build its summary from the cursor, and collect it
                 // along with all others of this MIME-type.
-                final Action action = new DataAction(mContext, mimeType, kind, dataId, cursor);
+                final Action action = new DataAction(context, mimeType, kind, dataId, cursor);
                 final boolean wasAdded = considerAdd(action, cache);
                 if (wasAdded) {
                     // Remember the default
@@ -700,7 +463,7 @@
 
             // If phone number, also insert as text message action
             if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && kind != null) {
-                final DataAction action = new DataAction(mContext, Constants.MIME_TYPE_SMS_ADDRESS,
+                final DataAction action = new DataAction(context, Constants.MIME_TYPE_SMS_ADDRESS,
                         kind, dataId, cursor);
                 considerAdd(action, cache);
             }
@@ -713,7 +476,7 @@
                 final DataKind imKind = accountTypes.getKindOrFallback(accountType,
                         Im.CONTENT_ITEM_TYPE);
                 if (imKind != null) {
-                    final DataAction action = new DataAction(mContext, Im.CONTENT_ITEM_TYPE, imKind,
+                    final DataAction action = new DataAction(context, Im.CONTENT_ITEM_TYPE, imKind,
                             dataId, cursor);
                     considerAdd(action, cache);
                     isIm = true;
@@ -726,7 +489,7 @@
                     final DataKind imKind = accountTypes.getKindOrFallback(accountType,
                             Im.CONTENT_ITEM_TYPE);
                     if (imKind != null) {
-                        final DataAction chatAction = new DataAction(mContext,
+                        final DataAction chatAction = new DataAction(context,
                                 Constants.MIME_TYPE_VIDEO_CHAT, imKind, dataId, cursor);
                         considerAdd(chatAction, cache);
                     }
@@ -760,7 +523,7 @@
             final int presence = cursor.getInt(DataQuery.CONTACT_PRESENCE);
             final int chatCapability = cursor.getInt(DataQuery.CONTACT_CHAT_CAPABILITY);
             final Drawable statusIcon = ContactPresenceIconUtil.getChatCapabilityIcon(
-                    mContext, presence, chatCapability);
+                    context, presence, chatCapability);
 
             setHeaderText(R.id.name, name);
             setHeaderImage(R.id.presence, statusIcon);
@@ -776,7 +539,7 @@
         if (mHasValidSocial && mMode != QuickContact.MODE_SMALL) {
             // Update status when valid was found
             setHeaderText(R.id.status, status.getStatus());
-            setHeaderText(R.id.timestamp, status.getTimestampLabel(mContext));
+            setHeaderText(R.id.timestamp, status.getTimestampLabel(context));
         }
 
         // Turn our list of actions into UI elements
@@ -790,7 +553,7 @@
         for (String mimeType : PRECEDING_MIMETYPES) {
             if (containedTypes.contains(mimeType)) {
                 hasData = true;
-                mTrack.addView(inflateAction(mimeType, cache));
+                mTrack.addView(inflateAction(mimeType, cache, mTrack));
                 containedTypes.remove(mimeType);
             }
         }
@@ -802,7 +565,7 @@
         for (String mimeType : FOLLOWING_MIMETYPES) {
             if (containedTypes.contains(mimeType)) {
                 hasData = true;
-                mTrack.addView(inflateAction(mimeType, cache));
+                mTrack.addView(inflateAction(mimeType, cache, mTrack));
                 containedTypes.remove(mimeType);
             }
         }
@@ -811,9 +574,11 @@
         if (containedTypes.contains(ClearDefaultsAction.PSEUDO_MIME_TYPE)) {
             final ClearDefaultsAction action = (ClearDefaultsAction) mActions.get(
                     ClearDefaultsAction.PSEUDO_MIME_TYPE).get(0);
-            final CheckableImageView view = obtainView();
+            final CheckableImageView view = (CheckableImageView) getLayoutInflater().inflate(
+                    R.layout.quickcontact_item, mTrack, false);
+
             view.setChecked(false);
-            final String description = mContext.getResources().getString(
+            final String description = context.getResources().getString(
                     R.string.quickcontact_clear_defaults_description);
             view.setContentDescription(description);
             view.setImageResource(R.drawable.ic_menu_settings_holo_light);
@@ -829,12 +594,13 @@
         if (remainingTypes.length > 0) hasData = true;
         Arrays.sort(remainingTypes);
         for (String mimeType : remainingTypes) {
-            mTrack.addView(inflateAction(mimeType, cache), index++);
+            mTrack.addView(inflateAction(mimeType, cache, mTrack), index++);
         }
 
         if (!hasData) {
             // When there is no data to display, add a TextView to show the user there's no data
-            View view = mInflater.inflate(R.layout.quickcontact_item_nodata, mTrack, false);
+            View view = getLayoutInflater().inflate(
+                    R.layout.quickcontact_item_nodata, mTrack, false);
             mTrack.addView(view, index++);
         }
     }
@@ -843,7 +609,9 @@
      * Clears the defaults currently set on the Contact
      */
     private void clearDefaults() {
+        final Context context = this;
         final Set<String> mimeTypesKeySet = mDefaultsMap.keySet();
+
         // Copy to array so that we can modify the HashMap below
         final String[] mimeTypes = new String[mimeTypesKeySet.size()];
         mimeTypesKeySet.toArray(mimeTypes);
@@ -851,9 +619,9 @@
         // Send clear default Intents, one by one
         for (String mimeType : mimeTypes) {
             final Action action = mDefaultsMap.get(mimeType);
-            final Intent intent =
-                    ContactSaveService.createClearPrimaryIntent(mContext, action.getDataId());
-            mContext.startService(intent);
+            final Intent intent = ContactSaveService.createClearPrimaryIntent(
+                    context, action.getDataId());
+            context.startService(intent);
             mDefaultsMap.remove(mimeType);
         }
 
@@ -864,7 +632,6 @@
                 for (int i = mTrack.getChildCount() - 1; i >= 0; i--) {
                     final CheckableImageView button = (CheckableImageView) mTrack.getChildAt(i);
                     if (button.getTag() instanceof ClearDefaultsAction) {
-                        releaseView(button);
                         mTrack.removeViewAt(i);
                         break;
                     }
@@ -888,35 +655,12 @@
     }
 
     /**
-     * Obtain a new {@link CheckableImageView} for a new chiclet, either by
-     * recycling one from {@link #mActionPool}, or by inflating a new one. When
-     * finished, use {@link #releaseView(CheckableImageView)} to return back into the pool for
-     * later recycling.
-     */
-    private synchronized CheckableImageView obtainView() {
-        CheckableImageView view = mActionPool.poll();
-        if (view == null || QuickContactActivity.FORCE_CREATE) {
-            view = (CheckableImageView) mInflater.inflate(R.layout.quickcontact_item, mTrack,
-                    false);
-        }
-        return view;
-    }
-
-    /**
-     * Return the given {@link CheckableImageView} into our internal pool for
-     * possible recycling during another pass.
-     */
-    private synchronized void releaseView(CheckableImageView view) {
-        mActionPool.offer(view);
-        mActionRecycled++;
-    }
-
-    /**
      * Inflate the in-track view for the action of the given MIME-type, collapsing duplicate values.
      * Will use the icon provided by the {@link DataKind}.
      */
-    private View inflateAction(String mimeType, ResolveCache resolveCache) {
-        final CheckableImageView view = obtainView();
+    private View inflateAction(String mimeType, ResolveCache resolveCache, ViewGroup root) {
+        final CheckableImageView view = (CheckableImageView) getLayoutInflater().inflate(
+                R.layout.quickcontact_item, root, false);
 
         // Add direct intent if single child, otherwise flag for multiple
         List<Action> children = mActions.get(mimeType);
@@ -996,6 +740,13 @@
                 expandAnimator.setDuration(ANIMATION_EXPAND_TIME);
                 expandAnimator.start();
 
+                expandAnimator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mBackground.clearBottomOverride();
+                    }
+                });
+
                 final ObjectAnimator fadeInAnimator = ObjectAnimator.ofFloat(mFooter,
                         "alpha", 0.0f, 1.0f);
                 fadeInAnimator.setDuration(ANIMATION_FADE_IN_TIME);
@@ -1008,9 +759,12 @@
     /** {@inheritDoc} */
     @Override
     public void onClick(View view) {
+        final Context context = this;
+
         final boolean isActionView = (view instanceof CheckableImageView);
         final CheckableImageView actionView = isActionView ? (CheckableImageView)view : null;
         final Object tag = view.getTag();
+
         if (tag instanceof ClearDefaultsAction) {
             // Do nothing if already open
             if (actionView == mLastAction) return;
@@ -1051,29 +805,28 @@
 
                         @Override
                         public View getView(int position, View convertView, ViewGroup parent) {
-                            final View result = convertView != null ? convertView :
-                                    mInflater.inflate(R.layout.quickcontact_default_item,
-                                    parent, false);
+                            if (convertView == null) {
+                                convertView = getLayoutInflater().inflate(
+                                        R.layout.quickcontact_default_item, parent, false);
+                            }
+
                             // Set action title based on summary value
                             final Action defaultAction = actions[position];
 
-                            TextView text1 = (TextView)result.findViewById(android.R.id.text1);
-                            TextView text2 = (TextView)result.findViewById(android.R.id.text2);
+                            final TextView text1 = (TextView) convertView.findViewById(
+                                    android.R.id.text1);
+                            final TextView text2 = (TextView) convertView.findViewById(
+                                    android.R.id.text2);
 
                             text1.setText(defaultAction.getHeader());
                             text2.setText(defaultAction.getBody());
 
-                            result.setTag(defaultAction);
-                            return result;
+                            convertView.setTag(defaultAction);
+                            return convertView;
                         }
                     });
 
                     animateExpand(true);
-                    // Make sure we resize to make room for ListView
-                    if (mDecor != null) {
-                        mDecor.forceLayout();
-                        mDecor.invalidate();
-                    }
                 }
             };
             if (mFooter.getVisibility() == View.VISIBLE) {
@@ -1122,24 +875,24 @@
                 public void run() {
                     // Incoming tag is concrete intent, so try launching
                     try {
-                        mContext.startActivity(action.getIntent());
+                        context.startActivity(action.getIntent());
                     } catch (ActivityNotFoundException e) {
-                        Toast.makeText(mContext, R.string.quickcontact_missing_app,
+                        Toast.makeText(context, R.string.quickcontact_missing_app,
                                 Toast.LENGTH_SHORT).show();
                     }
 
                     // Hide the resolution list, if present
                     setNewActionViewChecked(null);
-                    dismiss();
-                    mFooter.setVisibility(View.GONE);
 
                     // Set default?
                     final long dataId = action.getDataId();
                     if (makePrimary && dataId != -1) {
                         Intent serviceIntent = ContactSaveService.createSetSuperPrimaryIntent(
-                                mContext, dataId);
-                        mContext.startService(serviceIntent);
+                                context, dataId);
+                        context.startService(serviceIntent);
                     }
+
+                    hide(false);
                 }
             };
             if (isActionView && mFooter.getVisibility() == View.VISIBLE) {
@@ -1184,31 +937,31 @@
 
                     @Override
                     public View getView(int position, View convertView, ViewGroup parent) {
-                        final View result = convertView != null ? convertView :
-                                mInflater.inflate(R.layout.quickcontact_resolve_item,
-                                parent, false);
+                        if (convertView == null) {
+                            convertView = getLayoutInflater().inflate(
+                                    R.layout.quickcontact_resolve_item, parent, false);
+                        }
+
                         // Set action title based on summary value
                         final Action listAction = actionList.get(position);
 
-                        TextView text1 = (TextView)result.findViewById(android.R.id.text1);
-                        TextView text2 = (TextView)result.findViewById(android.R.id.text2);
+                        final TextView text1 = (TextView) convertView.findViewById(
+                                android.R.id.text1);
+                        final TextView text2 = (TextView) convertView.findViewById(
+                                android.R.id.text2);
 
                         text1.setText(listAction.getHeader());
                         text2.setText(listAction.getBody());
 
-                        result.setTag(listAction);
-                        return result;
+                        convertView.setTag(listAction);
+                        return convertView;
                     }
                 });
 
                 animateExpand(false);
-                // Make sure we resize to make room for ListView
-                if (mDecor != null) {
-                    mDecor.forceLayout();
-                    mDecor.invalidate();
-                }
             }
         };
+
         if (mFooter.getVisibility() == View.VISIBLE) {
             // If the expansion list is currently opened, animate its collapse and then
             // execute the target app
@@ -1219,191 +972,6 @@
         }
     }
 
-    @Override
-    public void onBackPressed() {
-        dismiss();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean dispatchKeyEvent(KeyEvent event) {
-        if (mWindow.superDispatchKeyEvent(event)) {
-            return true;
-        }
-        return event.dispatch(this, mDecor != null
-                ? mDecor.getKeyDispatcherState() : null, this);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_BACK) {
-            event.startTracking();
-            return true;
-        }
-
-        return false;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
-                && !event.isCanceled()) {
-            onBackPressed();
-            return true;
-        }
-
-        return false;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
-        return false;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
-        return false;
-    }
-
-    /** {@inheritDoc} */
-    public boolean dispatchKeyShortcutEvent(KeyEvent event) {
-        return mWindow.superDispatchKeyShortcutEvent(event);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
-        // TODO: make this window accessible
-        return false;
-    }
-
-    /**
-     * Detect if the given {@link MotionEvent} is outside the boundaries of this
-     * window, which usually means we should dismiss.
-     */
-    protected void detectEventOutside(MotionEvent event) {
-        if (event.getAction() == MotionEvent.ACTION_DOWN && mDecor != null) {
-            // Only try detecting outside events on down-press
-            mDecor.getHitRect(mRect);
-            final int x = (int)event.getX();
-            final int y = (int)event.getY();
-            if (!mRect.contains(x, y)) {
-                event.setAction(MotionEvent.ACTION_OUTSIDE);
-            }
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent event) {
-        detectEventOutside(event);
-        if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
-            dismiss();
-            return true;
-        }
-        return mWindow.superDispatchTouchEvent(event);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean dispatchTrackballEvent(MotionEvent event) {
-        return mWindow.superDispatchTrackballEvent(event);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean dispatchGenericMotionEvent(MotionEvent event) {
-        return mWindow.superDispatchGenericMotionEvent(event);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void onContentChanged() {
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean onCreatePanelMenu(int featureId, Menu menu) {
-        return false;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public View onCreatePanelView(int featureId) {
-        return null;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean onMenuItemSelected(int featureId, MenuItem item) {
-        return false;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean onMenuOpened(int featureId, Menu menu) {
-        return false;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void onPanelClosed(int featureId, Menu menu) {
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean onPreparePanel(int featureId, View view, Menu menu) {
-        return false;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean onSearchRequested() {
-        return false;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void onWindowAttributesChanged(android.view.WindowManager.LayoutParams attrs) {
-        if (mDecor != null) {
-            mWindowManager.updateViewLayout(mDecor, attrs);
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void onWindowFocusChanged(boolean hasFocus) {
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void onAttachedToWindow() {
-        // No actions
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void onDetachedFromWindow() {
-        // No actions
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
-        return null;
-    }
-
-    @Override
-    public void onActionModeStarted(ActionMode mode) {
-    }
-
-    @Override
-    public void onActionModeFinished(ActionMode mode) {
-    }
 
     private interface DataQuery {
         final String[] PROJECTION = new String[] {
diff --git a/src/com/android/contacts/util/NotifyingAsyncQueryHandler.java b/src/com/android/contacts/util/NotifyingAsyncQueryHandler.java
index 83fae29..c8cfc8d 100644
--- a/src/com/android/contacts/util/NotifyingAsyncQueryHandler.java
+++ b/src/com/android/contacts/util/NotifyingAsyncQueryHandler.java
@@ -29,8 +29,6 @@
  * <p>
  * This pattern can be used to perform background queries without leaking
  * {@link Context} objects.
- *
- * @hide pending API council review
  */
 public class NotifyingAsyncQueryHandler extends AsyncQueryHandler {
     private WeakReference<AsyncQueryListener> mListener;