Fix some QC issues

Bug:6449630

Change-Id: I5aec3ffdd3032bd3d037a053f5cb86fc9f0f16e0
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