Remove CircularRevealActivity

Replace CircularRevealActivity with a fragment that is tied to
the lifecycle of InCallActivity so that InCallActivity is the
only activity that is involved in the outgoing call process.

Simplify some of the intent creation logic that is used to launch
InCallActivity since we no longer need some of the flags that were
currently provided via various method overloads in
InCallPresenter.getAnimationIntent and getInCallIntent.

To further simplify this CL, a very rudimentary approach is taken to
track the state where we are starting up the UI but haven't received
a call from Telecom yet. This is done by a boolean flag set on the
InCallPresenter: mBoundAndWaitingForOutgoingCall.

Further changes will build upon and improve this.

Also set most elements inside primary_call_info.xml to
visibility: GONE by default to fix some animation jank where a
empty but not GONE view would continue to take up space.

Change-Id: Ic70857685680af6868d4b2153d942d73eef3ca56
diff --git a/InCallUI/res/layout/primary_call_info.xml b/InCallUI/res/layout/primary_call_info.xml
index 71369d4..cb9cc24 100644
--- a/InCallUI/res/layout/primary_call_info.xml
+++ b/InCallUI/res/layout/primary_call_info.xml
@@ -109,7 +109,8 @@
                     android:textColor="@color/incall_call_banner_subtext_color"
                     android:textSize="@dimen/call_label_text_size"
                     android:singleLine="true"
-                    android:textDirection="ltr" />
+                    android:textDirection="ltr"
+                    android:visibility="gone" />
 
                 <TextView android:id="@+id/phoneNumber"
                     android:layout_width="match_parent"
@@ -118,7 +119,8 @@
                     android:textAppearance="?android:attr/textAppearanceSmall"
                     android:textColor="@color/incall_call_banner_subtext_color"
                     android:textSize="@dimen/call_label_text_size"
-                    android:singleLine="true" />
+                    android:singleLine="true"
+                    android:visibility="gone" />
 
         </LinearLayout>
 
@@ -145,6 +147,7 @@
         android:textAppearance="?android:attr/textAppearanceSmall"
         android:textColor="@color/incall_call_banner_text_color"
         android:maxLines="1"
-        android:ellipsize="end" />
+        android:ellipsize="end"
+        android:visibility="gone" />
 
 </LinearLayout>  <!-- End of call_banner -->
diff --git a/InCallUI/res/values/styles.xml b/InCallUI/res/values/styles.xml
index e16d72c..dba7cc3 100644
--- a/InCallUI/res/values/styles.xml
+++ b/InCallUI/res/values/styles.xml
@@ -70,43 +70,12 @@
         <item name="android:textOff">@null</item>
     </style>
 
-    <style name="InCallAnimationStyle" parent="@android:style/Animation.Activity">
-        <!-- Suppress task-to-task animation happening during the transition from
-             OutgoingCallBroadcaster (and SipOptionHandler) to InCallActivity.
-             The transition unexpectedly happens during the transition (inside the phone task),
-             because InCallActivity is using android:launchMode="singleInstance".
-
-             - taskOpenEnterAnimation/taskOpenExitAnimation is used for the first time
-               InCallActivity instance is created.
-
-             - taskToFrontEnterAnimation/taskToFrontExitAnimation is used when InCallActivity
-               is already available.
-               (Note that InCallActivity won't be destroyed once it is created)
-
-             TODO: try removing the flag instead -->
-        <item name="android:taskOpenEnterAnimation">@null</item>
-        <item name="android:taskOpenExitAnimation">@anim/activity_open_exit</item>
-        <item name="android:taskToFrontEnterAnimation">@anim/activity_open_enter</item>
-        <item name="android:taskToFrontExitAnimation">@anim/activity_open_exit</item>
-    </style>
-
-    <style name="OutgoingCallAnimationStyle" parent="@android:style/Animation.Activity">
-        <item name="android:taskOpenEnterAnimation">@null</item>
-        <item name="android:taskOpenExitAnimation">@null</item>
-        <item name="android:activityOpenEnterAnimation">@null</item>
-        <item name="android:activityOpenExitAnimation">@null</item>
-        <item name="android:activityCloseEnterAnimation">@null</item>
-        <item name="android:activityCloseExitAnimation">@null</item>
-        <item name="android:taskToFrontEnterAnimation">@null</item>
-        <item name="android:taskToFrontExitAnimation">@null</item>
-    </style>
-
     <!-- Theme for the InCallActivity activity. Should have a transparent background for the
          circular reveal animation for a new outgoing call to work correctly. We don't just use
          Theme.Black.NoTitleBar directly, since we want any popups or dialogs from the
          InCallActivity to have the correct Material style. -->
     <style name="Theme.InCallScreen" parent="@android:style/Theme.Material.Light">
-        <item name="android:windowAnimationStyle">@style/InCallAnimationStyle</item>
+        <item name="android:windowAnimationStyle">@null</item>
         <item name="android:windowIsTranslucent">true</item>
         <item name="android:windowBackground">@android:color/transparent</item>
         <item name="android:windowContentOverlay">@null</item>
@@ -118,15 +87,6 @@
         <item name="android:buttonStyleToggle">@style/InCallCompoundButton</item>
     </style>
 
-    <style name="Theme.CircularRevealAnimation" parent="@android:style/Theme.Material.Light">
-        <item name="android:windowIsTranslucent">true</item>
-        <item name="android:windowContentOverlay">@null</item>
-        <item name="android:windowNoTitle">true</item>
-        <item name="android:colorPrimaryDark">@color/dialer_theme_color_dark</item>
-        <item name="android:windowBackground">@android:color/transparent</item>
-        <item name="android:windowAnimationStyle">@null</item>
-    </style>
-
     <style name="InCallPopupMenuStyle" parent="@android:style/Theme.Material.Light">
         <item name="android:textColorPrimary">@color/popup_menu_color</item>
     </style>
diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java
index 649176c..00e6196 100644
--- a/InCallUI/src/com/android/incallui/CallCardFragment.java
+++ b/InCallUI/src/com/android/incallui/CallCardFragment.java
@@ -24,7 +24,6 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.graphics.Point;
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
@@ -33,11 +32,9 @@
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
-import android.view.Display;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnLayoutChangeListener;
-import android.view.ViewAnimationUtils;
 import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
 import android.view.ViewTreeObserver;
@@ -51,7 +48,6 @@
 
 import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
 import com.android.contacts.common.widget.FloatingActionButtonController;
-import com.android.incallui.service.PhoneNumberService;
 import com.android.phone.common.animation.AnimUtils;
 
 import java.util.List;
@@ -63,7 +59,6 @@
         implements CallCardPresenter.CallCardUi {
 
     private AnimatorSet mAnimatorSet;
-    private int mRevealAnimationDuration;
     private int mShrinkAnimationDuration;
     private int mFabNormalDiameter;
     private int mFabSmallDiameter;
@@ -108,9 +103,6 @@
     private ImageButton mFloatingActionButton;
     private int mFloatingActionButtonVerticalOffset;
 
-    // Cached DisplayMetrics density.
-    private float mDensity;
-
     private float mTranslationOffset;
     private Animation mPulseAnimation;
 
@@ -132,7 +124,6 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        mRevealAnimationDuration = getResources().getInteger(R.integer.reveal_animation_duration);
         mShrinkAnimationDuration = getResources().getInteger(R.integer.shrink_animation_duration);
         mVideoAnimationDuration = getResources().getInteger(R.integer.video_animation_duration);
         mFloatingActionButtonVerticalOffset = getResources().getDimensionPixelOffset(
@@ -156,9 +147,6 @@
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
-        super.onCreateView(inflater, container, savedInstanceState);
-
-        mDensity = getResources().getDisplayMetrics().density;
         mTranslationOffset =
                 getResources().getDimensionPixelSize(R.dimen.call_card_anim_translate_y_offset);
 
@@ -230,6 +218,9 @@
 
         mPrimaryName.setElegantTextHeight(false);
         mCallStateLabel.setElegantTextHeight(false);
+
+        final LayoutTransition transition = mPrimaryCallInfo.getLayoutTransition();
+        transition.enableTransitionType(LayoutTransition.CHANGING);
     }
 
     @Override
@@ -425,7 +416,6 @@
     public void setPrimary(String number, String name, boolean nameIsNumber, String label,
             Drawable photo, boolean isSipCall) {
         Log.d(this, "Setting primary call");
-
         // set the name field.
         setPrimaryName(name, nameIsNumber);
 
@@ -842,13 +832,14 @@
         }
     }
 
-    public void animateForNewOutgoingCall(final Point touchPoint,
-            final boolean showCircularReveal) {
+    @Override
+    public void animateForNewOutgoingCall() {
         final ViewGroup parent = (ViewGroup) mPrimaryCallCardContainer.getParent();
 
         final ViewTreeObserver observer = getView().getViewTreeObserver();
 
-        mPrimaryCallInfo.getLayoutTransition().disableTransitionType(LayoutTransition.CHANGING);
+        final LayoutTransition transition = mPrimaryCallInfo.getLayoutTransition();
+        transition.disableTransitionType(LayoutTransition.CHANGING);
 
         observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
             @Override
@@ -862,21 +853,21 @@
                 final LayoutIgnoringListener listener = new LayoutIgnoringListener();
                 mPrimaryCallCardContainer.addOnLayoutChangeListener(listener);
 
-                // Prepare the state of views before the circular reveal animation
+                // Prepare the state of views before the slide animation
                 final int originalHeight = mPrimaryCallCardContainer.getHeight();
                 mPrimaryCallCardContainer.setBottom(parent.getHeight());
 
                 // Set up FAB.
                 mFloatingActionButtonContainer.setVisibility(View.GONE);
                 mFloatingActionButtonController.setScreenWidth(parent.getWidth());
+
                 mCallButtonsContainer.setAlpha(0);
                 mCallStateLabel.setAlpha(0);
                 mPrimaryName.setAlpha(0);
                 mCallTypeLabel.setAlpha(0);
                 mCallNumberAndLabel.setAlpha(0);
 
-                final Animator animator = getOutgoingCallAnimator(touchPoint,
-                        parent.getHeight(), originalHeight, showCircularReveal);
+                final Animator animator = getShrinkAnimator(parent.getHeight(), originalHeight);
 
                 animator.addListener(new AnimatorListenerAdapter() {
                     @Override
@@ -990,41 +981,6 @@
         return shrinkAnimator;
     }
 
-    private Animator getRevealAnimator(Point touchPoint) {
-        final Activity activity = getActivity();
-        final View view  = activity.getWindow().getDecorView();
-        final Display display = activity.getWindowManager().getDefaultDisplay();
-        final Point size = new Point();
-        display.getSize(size);
-
-        int startX = size.x / 2;
-        int startY = size.y / 2;
-        if (touchPoint != null) {
-            startX = touchPoint.x;
-            startY = touchPoint.y;
-        }
-
-        final Animator valueAnimator = ViewAnimationUtils.createCircularReveal(view,
-                startX, startY, 0, Math.max(size.x, size.y));
-        valueAnimator.setDuration(mRevealAnimationDuration);
-        return valueAnimator;
-    }
-
-    private Animator getOutgoingCallAnimator(Point touchPoint, int startHeight, int endHeight,
-            boolean showCircularReveal) {
-
-        final Animator shrinkAnimator = getShrinkAnimator(startHeight, endHeight);
-
-        if (!showCircularReveal) {
-            return shrinkAnimator;
-        }
-
-        final Animator revealAnimator = getRevealAnimator(touchPoint);
-        final AnimatorSet animatorSet = new AnimatorSet();
-        animatorSet.playSequentially(revealAnimator, shrinkAnimator);
-        return animatorSet;
-    }
-
     private void assignTranslateAnimation(View view, int offset) {
         view.setTranslationY(mTranslationOffset * offset);
         view.animate().translationY(0).alpha(1).withLayer()
@@ -1045,7 +1001,10 @@
         setViewStatePostAnimation(mCallStateIcon);
 
         mPrimaryCallCardContainer.removeOnLayoutChangeListener(layoutChangeListener);
-        mPrimaryCallInfo.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
+
+        final LayoutTransition transition = mPrimaryCallInfo.getLayoutTransition();
+        transition.enableTransitionType(LayoutTransition.CHANGING);
+
         mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY);
     }
 
diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java
index cb378a6..ab93c2d 100644
--- a/InCallUI/src/com/android/incallui/CallCardPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java
@@ -17,6 +17,8 @@
 package com.android.incallui;
 
 import android.Manifest;
+import android.app.Activity;
+import android.app.FragmentManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -34,6 +36,7 @@
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
+import com.android.incallui.CircularRevealFragment.OnCircularRevealCompleteListener;
 import com.android.incallui.ContactInfoCache.ContactCacheEntry;
 import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
 import com.android.incallui.InCallPresenter.InCallDetailsListener;
@@ -743,5 +746,6 @@
         void setProgressSpinnerVisible(boolean visible);
         void showManageConferenceCallButton(boolean visible);
         boolean isManageConferenceVisible();
+        void animateForNewOutgoingCall();
     }
 }
diff --git a/InCallUI/src/com/android/incallui/CircularRevealActivity.java b/InCallUI/src/com/android/incallui/CircularRevealActivity.java
deleted file mode 100644
index 4dc58ba..0000000
--- a/InCallUI/src/com/android/incallui/CircularRevealActivity.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2014 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.incallui;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.Outline;
-import android.graphics.Point;
-import android.os.Bundle;
-import android.support.v4.content.LocalBroadcastManager;
-import android.view.Display;
-import android.view.View;
-import android.view.ViewAnimationUtils;
-import android.view.ViewOutlineProvider;
-import android.view.ViewTreeObserver;
-import android.view.ViewTreeObserver.OnPreDrawListener;
-
-import com.android.contacts.common.interactions.TouchPointManager;
-import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
-
-/**
- * Lightweight activity used to display a circular reveal while InCallActivity is starting up.
- * A BroadcastReceiver is used to listen to broadcasts from a LocalBroadcastManager to finish
- * the activity at suitable times.
- */
-public class CircularRevealActivity extends Activity {
-    private static final int REVEAL_DURATION = 333;
-    public static final String EXTRA_THEME_COLORS = "extra_theme_colors";
-    public static final String ACTION_CLEAR_DISPLAY = "action_clear_display";
-
-    final BroadcastReceiver mClearDisplayReceiver = new BroadcastReceiver( ) {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            clearDisplay();
-        }
-    };
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        overridePendingTransition(0, 0);
-        setContentView(R.layout.outgoing_call_animation);
-        prepareDecorViewFromIntent(getIntent());
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        if (!InCallPresenter.getInstance().isServiceBound()) {
-            clearDisplay();
-        }
-        final IntentFilter filter = new IntentFilter();
-        filter.addAction(ACTION_CLEAR_DISPLAY);
-        LocalBroadcastManager.getInstance(this).registerReceiver(mClearDisplayReceiver, filter);
-    }
-
-    @Override
-    protected void onStop() {
-        LocalBroadcastManager.getInstance(this).unregisterReceiver(mClearDisplayReceiver);
-        super.onStop();
-    }
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        setIntent(intent);
-        prepareDecorViewFromIntent(intent);
-    }
-
-    private void prepareDecorViewFromIntent(Intent intent) {
-        final Point touchPoint = intent.getParcelableExtra(TouchPointManager.TOUCH_POINT);
-        final MaterialPalette palette = intent.getParcelableExtra(EXTRA_THEME_COLORS);
-        setupDecorView(touchPoint, palette);
-    }
-
-    private void setupDecorView(final Point touchPoint, MaterialPalette palette) {
-        final View view  = getWindow().getDecorView();
-
-        // The circle starts from an initial size of 0 so clip it such that it is invisible. When
-        // the animation later starts, this clip will be clobbered by the circular reveal clip.
-        // See ViewAnimationUtils.createCircularReveal.
-        view.setOutlineProvider(new ViewOutlineProvider() {
-            @Override
-            public void getOutline(View view, Outline outline) {
-                // Using (0, 0, 0, 0) will not work since the outline will simply be treated as
-                // an empty outline.
-                outline.setOval(-1, -1, 0, 0);
-            }
-        });
-        view.setClipToOutline(true);
-
-        if (palette != null) {
-            view.findViewById(R.id.outgoing_call_animation_circle).setBackgroundColor(
-                    palette.mPrimaryColor);
-            getWindow().setStatusBarColor(palette.mSecondaryColor);
-        }
-
-        view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
-            @Override
-            public boolean onPreDraw() {
-                final ViewTreeObserver vto = view.getViewTreeObserver();
-                if (vto.isAlive()) {
-                    vto.removeOnPreDrawListener(this);
-                }
-                final Animator animator = getRevealAnimator(touchPoint);
-                // Since this animator is a RenderNodeAnimator (native animator), add an arbitary
-                // start delay to force the onAnimationStart callback to happen later on the UI
-                // thread. Otherwise it would happen right away inside animator.start()
-                animator.setStartDelay(5);
-                animator.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                        InCallPresenter.getInstance().onCircularRevealStarted(
-                                CircularRevealActivity.this);
-                    }
-
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        view.setClipToOutline(false);
-                        super.onAnimationEnd(animation);
-                    }
-                });
-                animator.start();
-                return false;
-            }
-        });
-    }
-
-    private void clearDisplay() {
-        getWindow().getDecorView().setVisibility(View.INVISIBLE);
-        finish();
-    }
-
-    @Override
-    public void onBackPressed() {
-        return;
-    }
-
-    public static void sendClearDisplayBroadcast(Context context) {
-        LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(ACTION_CLEAR_DISPLAY));
-    }
-
-    private Animator getRevealAnimator(Point touchPoint) {
-        final View view  = getWindow().getDecorView();
-        final Display display = getWindowManager().getDefaultDisplay();
-        final Point size = new Point();
-        display.getSize(size);
-
-        int startX = size.x / 2;
-        int startY = size.y / 2;
-        if (touchPoint != null) {
-            startX = touchPoint.x;
-            startY = touchPoint.y;
-        }
-
-        final Animator valueAnimator = ViewAnimationUtils.createCircularReveal(view,
-                startX, startY, 0, Math.max(size.x, size.y));
-        valueAnimator.setDuration(REVEAL_DURATION);
-        return valueAnimator;
-    }
-}
diff --git a/InCallUI/src/com/android/incallui/CircularRevealFragment.java b/InCallUI/src/com/android/incallui/CircularRevealFragment.java
new file mode 100644
index 0000000..c282179
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/CircularRevealFragment.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2014 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.incallui;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.graphics.Outline;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnPreDrawListener;
+
+import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
+
+public class CircularRevealFragment extends Fragment {
+    static final String TAG = "CircularRevealFragment";
+
+    private Point mTouchPoint;
+    private OnCircularRevealCompleteListener mListener;
+    private boolean mAnimationStarted;
+
+    interface OnCircularRevealCompleteListener {
+        public void onCircularRevealComplete(FragmentManager fm);
+    }
+
+    public static void startCircularReveal(FragmentManager fm, Point touchPoint,
+            OnCircularRevealCompleteListener listener) {
+        fm.beginTransaction().add(R.id.main, new CircularRevealFragment(touchPoint, listener), TAG)
+                .commitAllowingStateLoss();
+    }
+
+    public static void endCircularReveal(FragmentManager fm) {
+        final Fragment fragment = fm.findFragmentByTag(TAG);
+        if (fragment != null) {
+            fm.beginTransaction().remove(fragment).commitAllowingStateLoss();
+        }
+    }
+
+    /**
+     * Empty constructor used only by the {@link FragmentManager}.
+     */
+    public CircularRevealFragment() {}
+
+    public CircularRevealFragment(Point touchPoint, OnCircularRevealCompleteListener listener) {
+        mTouchPoint = touchPoint;
+        mListener = listener;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (!mAnimationStarted) {
+            // Only run the animation once for each instance of the fragment
+            startOutgoingAnimation(InCallPresenter.getInstance().getThemeColors());
+        }
+        mAnimationStarted = true;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.outgoing_call_animation, container, false);
+    }
+
+    public void startOutgoingAnimation(MaterialPalette palette) {
+        final Activity activity = getActivity();
+        if (activity == null) {
+            Log.w(this, "Asked to do outgoing call animation when not attached");
+            return;
+        }
+
+        final View view  = activity.getWindow().getDecorView();
+
+        // The circle starts from an initial size of 0 so clip it such that it is invisible.
+        // Otherwise the first frame is drawn with a fully opaque screen which causes jank. When
+        // the animation later starts, this clip will be clobbered by the circular reveal clip.
+        // See ViewAnimationUtils.createCircularReveal.
+        view.setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                // Using (0, 0, 0, 0) will not work since the outline will simply be treated as
+                // an empty outline.
+                outline.setOval(-1, -1, 0, 0);
+            }
+        });
+        view.setClipToOutline(true);
+
+        if (palette != null) {
+            view.findViewById(R.id.outgoing_call_animation_circle).setBackgroundColor(
+                    palette.mPrimaryColor);
+            activity.getWindow().setStatusBarColor(palette.mSecondaryColor);
+        }
+
+        view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                final ViewTreeObserver vto = view.getViewTreeObserver();
+                if (vto.isAlive()) {
+                    vto.removeOnPreDrawListener(this);
+                }
+                final Animator animator = getRevealAnimator(mTouchPoint);
+                animator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        view.setClipToOutline(false);
+                        if (mListener != null) {
+                            mListener.onCircularRevealComplete(getFragmentManager());
+                        }
+                    }
+                });
+                animator.start();
+                return false;
+            }
+        });
+    }
+
+    private Animator getRevealAnimator(Point touchPoint) {
+        final Activity activity = getActivity();
+        final View view  = activity.getWindow().getDecorView();
+        final Display display = activity.getWindowManager().getDefaultDisplay();
+        final Point size = new Point();
+        display.getSize(size);
+
+        int startX = size.x / 2;
+        int startY = size.y / 2;
+        if (touchPoint != null) {
+            startX = touchPoint.x;
+            startY = touchPoint.y;
+        }
+
+        final Animator valueAnimator = ViewAnimationUtils.createCircularReveal(view,
+                startX, startY, 0, Math.max(size.x, size.y));
+        valueAnimator.setDuration(getResources().getInteger(R.integer.reveal_animation_duration));
+        return valueAnimator;
+    }
+}
diff --git a/InCallUI/src/com/android/incallui/InCallActivity.java b/InCallUI/src/com/android/incallui/InCallActivity.java
index 32bee93..f692627 100644
--- a/InCallUI/src/com/android/incallui/InCallActivity.java
+++ b/InCallUI/src/com/android/incallui/InCallActivity.java
@@ -66,7 +66,6 @@
     public static final String SHOW_DIALPAD_EXTRA = "InCallActivity.show_dialpad";
     public static final String DIALPAD_TEXT_EXTRA = "InCallActivity.dialpad_text";
     public static final String NEW_OUTGOING_CALL_EXTRA = "InCallActivity.new_outgoing_call";
-    public static final String SHOW_CIRCULAR_REVEAL_EXTRA = "InCallActivity.show_circular_reveal";
 
     private CallButtonFragment mCallButtonFragment;
     private CallCardFragment mCallCardFragment;
@@ -489,12 +488,9 @@
                     }
                 }
 
-                // This is only true in the case where an outgoing call is initiated by tapping
-                // on the "Select account dialog", in which case we skip the initial animation. In
-                // most other cases the circular reveal is done by OutgoingCallAnimationActivity.
-                final boolean showCircularReveal =
-                        intent.getBooleanExtra(SHOW_CIRCULAR_REVEAL_EXTRA, false);
-                mCallCardFragment.animateForNewOutgoingCall(touchPoint, showCircularReveal);
+                // Start animation for new outgoing call
+                CircularRevealFragment.startCircularReveal(getFragmentManager(), touchPoint,
+                        InCallPresenter.getInstance());
 
                 // InCallActivity is responsible for disconnecting a new outgoing call if there
                 // is no way of making it (i.e. no valid call capable accounts)
diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
index ce6d439..82ce58c 100644
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/InCallPresenter.java
@@ -16,14 +16,13 @@
 
 package com.android.incallui;
 
-import android.app.Activity;
+import android.app.FragmentManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.graphics.Point;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Handler;
 import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccount;
 import android.telecom.Phone;
@@ -57,7 +56,8 @@
  * that want to listen in on the in-call state changes.
  * TODO: This class has become more of a state machine at this point.  Consider renaming.
  */
-public class InCallPresenter implements CallList.Listener, InCallPhoneListener {
+public class InCallPresenter implements CallList.Listener, InCallPhoneListener,
+        CircularRevealFragment.OnCircularRevealCompleteListener {
 
     private static final String EXTRA_FIRST_TIME_SHOWN =
             "com.android.incallui.intent.extra.FIRST_TIME_SHOWN";
@@ -97,6 +97,16 @@
     private boolean mAccountSelectionCancelled = false;
     private InCallCameraManager mInCallCameraManager = null;
 
+    /**
+     * Whether or not we are currently bound and waiting for Telecom to send us a new call.
+     */
+    private boolean mBoundAndWaitingForOutgoingCall;
+    /**
+     * If there is no actual call currently in the call list, this will be used as a fallback
+     * to determine the theme color for InCallUI.
+     */
+    private PhoneAccountHandle mPendingPhoneAccountHandle;
+
     private final Phone.Listener mPhoneListener = new Phone.Listener() {
         @Override
         public void onBringToForeground(Phone phone, boolean showDialpad) {
@@ -105,6 +115,9 @@
         }
         @Override
         public void onCallAdded(Phone phone, android.telecom.Call call) {
+            // Since a call has been added we are no longer waiting for Telecom to send us a
+            // call.
+            setBoundAndWaitingForOutgoingCall(false, null);
             call.addListener(mCallListener);
         }
         @Override
@@ -156,20 +169,6 @@
      */
     private boolean mIsActivityPreviouslyStarted = false;
 
-
-    /**
-     * Whether or not to wait for the circular reveal animation to be started, to avoid stopping
-     * the circular reveal animation activity before the animation is initiated.
-     */
-    private boolean mWaitForRevealAnimationStart = false;
-
-    /**
-     * Whether or not the CircularRevealAnimationActivity has started.
-     */
-    private boolean mCircularRevealActivityStarted = false;
-
-    private boolean mShowDialpadOnStart = false;
-
     /**
      * Whether or not InCallService is bound to Telecom.
      */
@@ -177,8 +176,6 @@
 
     private Phone mPhone;
 
-    private Handler mHandler = new Handler();
-
     /** Display colors for the UI. Consists of a primary color and secondary (darker) color */
     private MaterialPalette mThemeColors;
 
@@ -261,13 +258,6 @@
     }
 
     private void attemptFinishActivity() {
-        mWaitForRevealAnimationStart = false;
-
-        Context context = mContext != null ? mContext : mInCallActivity;
-        if (context != null) {
-            CircularRevealActivity.sendClearDisplayBroadcast(context);
-        }
-
         final boolean doFinish = (mInCallActivity != null && isActivityStarted());
         Log.i(this, "Hide in call UI: " + doFinish);
         if (doFinish) {
@@ -457,7 +447,7 @@
     /**
      * Given the call list, return the state in which the in-call screen should be.
      */
-    public static InCallState getPotentialStateFromCallList(CallList callList) {
+    public InCallState getPotentialStateFromCallList(CallList callList) {
 
         InCallState newState = InCallState.NO_CALLS;
 
@@ -479,9 +469,40 @@
             newState = InCallState.INCALL;
         }
 
+        if (newState == InCallState.NO_CALLS) {
+            if (mBoundAndWaitingForOutgoingCall) {
+                return InCallState.OUTGOING;
+            }
+        }
+
         return newState;
     }
 
+    public boolean isBoundAndWaitingForOutgoingCall() {
+        return mBoundAndWaitingForOutgoingCall;
+    }
+
+    public void setBoundAndWaitingForOutgoingCall(boolean isBound, PhoneAccountHandle handle) {
+        // NOTE: It is possible for there to be a race and have handle become null before
+        // the circular reveal starts. This should not cause any problems because CallCardFragment
+        // should fallback to the actual call in the CallList at that point in time to determine
+        // the theme color.
+        Log.i(this, "setBoundAndWaitingForOutgoingCall: " + isBound);
+        mBoundAndWaitingForOutgoingCall = isBound;
+        mPendingPhoneAccountHandle = handle;
+        if (isBound && mInCallState == InCallState.NO_CALLS) {
+            mInCallState = InCallState.OUTGOING;
+        }
+    }
+
+    @Override
+    public void onCircularRevealComplete(FragmentManager fm) {
+        if (mInCallActivity != null) {
+            mInCallActivity.getCallCardFragment().animateForNewOutgoingCall();
+            CircularRevealFragment.endCircularReveal(mInCallActivity.getFragmentManager());
+        }
+    }
+
     public void addIncomingCallListener(IncomingCallListener listener) {
         Preconditions.checkNotNull(listener);
         mIncomingCallListeners.add(listener);
@@ -714,8 +735,6 @@
 
         if (showing) {
             mIsActivityPreviouslyStarted = true;
-        } else {
-            CircularRevealActivity.sendClearDisplayBroadcast(mContext);
         }
 
         for (InCallUiListener listener : mInCallUiListeners) {
@@ -1125,35 +1144,8 @@
     }
 
     public void showInCall(final boolean showDialpad, final boolean newOutgoingCall) {
-        if (mCircularRevealActivityStarted) {
-            mWaitForRevealAnimationStart = true;
-            mShowDialpadOnStart = showDialpad;
-            Log.i(this, "Waiting for circular reveal completion to show InCallActivity");
-        } else {
-            Log.i(this, "Showing InCallActivity immediately");
-            mContext.startActivity(getInCallIntent(showDialpad, newOutgoingCall,
-                    newOutgoingCall /* showCircularReveal */));
-        }
-    }
-
-    public void onCircularRevealStarted(final Activity activity) {
-        mCircularRevealActivityStarted = false;
-        if (mWaitForRevealAnimationStart) {
-            mWaitForRevealAnimationStart = false;
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    Log.i(this, "Showing InCallActivity after circular reveal");
-                    final Intent intent =
-                            getInCallIntent(mShowDialpadOnStart, true, false, false);
-                    activity.startActivity(intent);
-                    mShowDialpadOnStart = false;
-                }
-            });
-        } else if (!mServiceBound) {
-            CircularRevealActivity.sendClearDisplayBroadcast(mContext);
-            return;
-        }
+        Log.i(this, "Showing InCallActivity");
+        mContext.startActivity(getInCallIntent(showDialpad, newOutgoingCall));
     }
 
     public void onServiceBind() {
@@ -1161,6 +1153,7 @@
     }
 
     public void onServiceUnbind() {
+        InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(false, null);
         mServiceBound = false;
     }
 
@@ -1185,43 +1178,26 @@
 
         final PhoneAccountHandle accountHandle =
                 intent.getParcelableExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
-        final MaterialPalette colors = getColorsFromPhoneAccountHandle(accountHandle);
         final Point touchPoint = extras.getParcelable(TouchPointManager.TOUCH_POINT);
 
-        mCircularRevealActivityStarted = true;
-        mContext.startActivity(getAnimationIntent(touchPoint, colors));
+        InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(true, accountHandle);
+
+        final Intent incallIntent = getInCallIntent(false, true);
+        incallIntent.putExtra(TouchPointManager.TOUCH_POINT, touchPoint);
+        mContext.startActivity(incallIntent);
     }
 
-    private Intent getAnimationIntent(Point touchPoint, MaterialPalette palette) {
-        final Intent intent = new Intent(mContext, CircularRevealActivity.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
-                | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
-        intent.putExtra(TouchPointManager.TOUCH_POINT, touchPoint);
-        intent.putExtra(CircularRevealActivity.EXTRA_THEME_COLORS, palette);
-        return intent;
-    }
-
-    public Intent getInCallIntent(boolean showDialpad, boolean newOutgoingCall,
-            boolean showCircularReveal) {
-        return getInCallIntent(showDialpad, newOutgoingCall, showCircularReveal, true);
-    }
-
-    public Intent getInCallIntent(boolean showDialpad, boolean newOutgoingCall,
-            boolean showCircularReveal, boolean newTask) {
+    public Intent getInCallIntent(boolean showDialpad, boolean newOutgoingCall) {
         final Intent intent = new Intent(Intent.ACTION_MAIN, null);
         intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
-                | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
-        if (newTask) {
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        }
+                | Intent.FLAG_ACTIVITY_NO_USER_ACTION
+                | Intent.FLAG_ACTIVITY_NEW_TASK);
 
         intent.setClass(mContext, InCallActivity.class);
         if (showDialpad) {
             intent.putExtra(InCallActivity.SHOW_DIALPAD_EXTRA, true);
         }
         intent.putExtra(InCallActivity.NEW_OUTGOING_CALL_EXTRA, newOutgoingCall);
-        intent.putExtra(InCallActivity.SHOW_CIRCULAR_REVEAL_EXTRA, showCircularReveal);
         return intent;
     }
 
@@ -1359,7 +1335,11 @@
     }
 
     private MaterialPalette getColorsFromCall(Call call) {
-        return getColorsFromPhoneAccountHandle(call == null ? null : call.getAccountHandle());
+        if (call == null) {
+            return getColorsFromPhoneAccountHandle(mPendingPhoneAccountHandle);
+        } else {
+            return getColorsFromPhoneAccountHandle(call.getAccountHandle());
+        }
     }
 
     private MaterialPalette getColorsFromPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) {
diff --git a/InCallUI/src/com/android/incallui/StatusBarNotifier.java b/InCallUI/src/com/android/incallui/StatusBarNotifier.java
index 99392d9..89192f8 100644
--- a/InCallUI/src/com/android/incallui/StatusBarNotifier.java
+++ b/InCallUI/src/com/android/incallui/StatusBarNotifier.java
@@ -627,8 +627,7 @@
     private PendingIntent createLaunchPendingIntent() {
 
         final Intent intent = InCallPresenter.getInstance().getInCallIntent(
-                false /* showDialpad */, false /* newOutgoingCall */,
-                        false /* showCircularReveal */);
+                false /* showDialpad */, false /* newOutgoingCall */);
 
         // PendingIntent that can be used to launch the InCallActivity.  The
         // system fires off this intent if the user pulls down the windowshade