Remove bubble from AOSP.

Bug: 67605985
Test: NewBubbleImplTest, NewBubbleImplIntegrationTest, NewReturnToCallControllerTest
PiperOrigin-RevId: 184026033
Change-Id: Ie141ce9a0265ce3a08c01943cdeb94e2cd962e9f
diff --git a/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java b/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
index 7d277d4..3f90e6a 100644
--- a/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
+++ b/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
@@ -37,6 +37,7 @@
 import com.android.incallui.calllocation.stub.StubCallLocationModule;
 import com.android.incallui.maps.stub.StubMapsModule;
 import com.android.incallui.speakeasy.StubSpeakEasyModule;
+import com.android.newbubble.stub.StubNewBubbleModule;
 import com.android.voicemail.impl.VoicemailModule;
 import dagger.Component;
 import javax.inject.Singleton;
@@ -59,6 +60,7 @@
     StubCallLocationModule.class,
     StubDuoModule.class,
     StubEnrichedCallModule.class,
+    StubNewBubbleModule.class,
     StubFeedbackModule.class,
     StubMapsModule.class,
     StubSimSuggestionModule.class,
diff --git a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
index 7e61af4..d8efd0a 100644
--- a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
+++ b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
@@ -37,6 +37,7 @@
 import com.android.incallui.calllocation.CallLocationComponent;
 import com.android.incallui.maps.MapsComponent;
 import com.android.incallui.speakeasy.SpeakEasyComponent;
+import com.android.newbubble.NewBubbleComponent;
 import com.android.voicemail.VoicemailComponent;
 
 /**
@@ -56,6 +57,7 @@
         FeedbackComponent.HasComponent,
         MainComponent.HasComponent,
         MapsComponent.HasComponent,
+        NewBubbleComponent.HasComponent,
         PhoneLookupComponent.HasComponent,
         PhoneNumberGeoUtilComponent.HasComponent,
         PreCallComponent.HasComponent,
diff --git a/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java b/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
index ff1b09d..4da0f92 100644
--- a/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
+++ b/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
@@ -37,6 +37,7 @@
 import com.android.incallui.calllocation.impl.CallLocationModule;
 import com.android.incallui.maps.impl.MapsModule;
 import com.android.incallui.speakeasy.StubSpeakEasyModule;
+import com.android.newbubble.stub.StubNewBubbleModule;
 import com.android.voicemail.impl.VoicemailModule;
 import dagger.Component;
 import javax.inject.Singleton;
@@ -64,6 +65,7 @@
     StubDuoModule.class,
     StubEnrichedCallModule.class,
     StubFeedbackModule.class,
+    StubNewBubbleModule.class,
     StubSimSuggestionModule.class,
     StubSpamModule.class,
     StubSpeakEasyModule.class,
diff --git a/java/com/android/incallui/NewReturnToCallController.java b/java/com/android/incallui/NewReturnToCallController.java
index 4c3c2a2..0b637f8 100644
--- a/java/com/android/incallui/NewReturnToCallController.java
+++ b/java/com/android/incallui/NewReturnToCallController.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
+import android.provider.Settings;
 import android.support.annotation.NonNull;
 import android.support.annotation.VisibleForTesting;
 import android.telecom.CallAudioState;
@@ -41,6 +42,7 @@
 import com.android.incallui.speakerbuttonlogic.SpeakerButtonInfo;
 import com.android.incallui.speakerbuttonlogic.SpeakerButtonInfo.IconSize;
 import com.android.newbubble.NewBubble;
+import com.android.newbubble.NewBubbleComponent;
 import com.android.newbubble.NewBubbleInfo;
 import com.android.newbubble.NewBubbleInfo.Action;
 import java.lang.ref.WeakReference;
@@ -58,6 +60,8 @@
   @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
   NewBubble bubble;
 
+  private static Boolean canShowBubblesForTesting = null;
+
   private CallAudioState audioState;
 
   private final PendingIntent toggleSpeaker;
@@ -132,13 +136,32 @@
     startContactInfoSearch();
   }
 
+  /**
+   * Determines whether bubbles can be shown based on permissions obtained. This should be checked
+   * before attempting to create a Bubble.
+   *
+   * @return true iff bubbles are able to be shown.
+   * @see Settings#canDrawOverlays(Context)
+   */
+  private static boolean canShowBubbles(@NonNull Context context) {
+    return canShowBubblesForTesting != null
+        ? canShowBubblesForTesting
+        : Settings.canDrawOverlays(context);
+  }
+
+  @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+  static void setCanShowBubblesForTesting(boolean canShowBubbles) {
+    canShowBubblesForTesting = canShowBubbles;
+  }
+
   @VisibleForTesting
   public NewBubble startBubble() {
-    if (!NewBubble.canShowBubbles(context)) {
+    if (!canShowBubbles(context)) {
       LogUtil.i("ReturnToCallController.startNewBubble", "can't show bubble, no permission");
       return null;
     }
-    NewBubble returnToCallBubble = NewBubble.createBubble(context, generateBubbleInfo());
+    NewBubble returnToCallBubble = NewBubbleComponent.get(context).getNewBubble();
+    returnToCallBubble.setBubbleInfo(generateBubbleInfo());
     returnToCallBubble.show();
     return returnToCallBubble;
   }
diff --git a/java/com/android/newbubble/AndroidManifest.xml b/java/com/android/newbubble/AndroidManifest.xml
deleted file mode 100644
index 048f8cf..0000000
--- a/java/com/android/newbubble/AndroidManifest.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
-  ~ Copyright (C) 2017 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
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.newbubble">
-
-  <uses-sdk android:minSdkVersion="23"/>
-  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
-</manifest>
diff --git a/java/com/android/newbubble/BottomActionViewController.java b/java/com/android/newbubble/BottomActionViewController.java
deleted file mode 100644
index b480ac9..0000000
--- a/java/com/android/newbubble/BottomActionViewController.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2017 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.newbubble;
-
-import android.content.Context;
-import android.graphics.PixelFormat;
-import android.support.v4.os.BuildCompat;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.animation.LinearInterpolator;
-
-/** Controller for showing and hiding bubble bottom action view. */
-final class BottomActionViewController {
-
-  // This delay controls how long to wait before we show the target when the user first moves
-  // the bubble, to prevent the bottom action view from animating if the user just wants to fling
-  // the bubble.
-  private static final int SHOW_TARGET_DELAY = 100;
-  private static final int SHOW_HIDE_TARGET_DURATION = 175;
-  private static final int HIGHLIGHT_TARGET_DURATION = 150;
-  private static final float HIGHLIGHT_TARGET_SCALE = 1.3f;
-  private static final float UNHIGHLIGHT_TARGET_ALPHA = 0.38f;
-
-  private final Context context;
-  private final WindowManager windowManager;
-  private final int gradientHeight;
-  private final int textOffsetSize;
-  private int bottomActionViewTop;
-
-  private View bottomActionView;
-  private View dismissView;
-  private View endCallView;
-
-  private boolean dismissHighlighted;
-  private boolean endCallHighlighted;
-
-  public BottomActionViewController(Context context) {
-    this.context = context;
-    windowManager = context.getSystemService(WindowManager.class);
-    gradientHeight =
-        context.getResources().getDimensionPixelSize(R.dimen.bubble_bottom_action_view_height);
-    textOffsetSize =
-        context.getResources().getDimensionPixelSize(R.dimen.bubble_bottom_action_text_offset);
-  }
-
-  /** Creates and show the bottom action view. */
-  public void createAndShowBottomActionView() {
-    if (bottomActionView != null) {
-      return;
-    }
-
-    // Create a new view for the dismiss target
-    bottomActionView = LayoutInflater.from(context).inflate(R.layout.bottom_action_base, null);
-    bottomActionView.setAlpha(0);
-
-    // Sub views
-    dismissView = bottomActionView.findViewById(R.id.bottom_action_dismiss_layout);
-    endCallView = bottomActionView.findViewById(R.id.bottom_action_end_call_layout);
-
-    // Add the target to the window
-    // TODO(yueg): use TYPE_NAVIGATION_BAR_PANEL to draw over navigation bar
-    bottomActionViewTop = context.getResources().getDisplayMetrics().heightPixels - gradientHeight;
-    LayoutParams layoutParams =
-        new LayoutParams(
-            LayoutParams.MATCH_PARENT,
-            gradientHeight,
-            0,
-            bottomActionViewTop,
-            BuildCompat.isAtLeastO()
-                ? LayoutParams.TYPE_APPLICATION_OVERLAY
-                : LayoutParams.TYPE_SYSTEM_OVERLAY,
-            LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                | LayoutParams.FLAG_NOT_TOUCHABLE
-                | LayoutParams.FLAG_NOT_FOCUSABLE,
-            PixelFormat.TRANSLUCENT);
-    layoutParams.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
-    windowManager.addView(bottomActionView, layoutParams);
-    bottomActionView.setSystemUiVisibility(
-        View.SYSTEM_UI_FLAG_LAYOUT_STABLE
-            | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
-            | View.SYSTEM_UI_FLAG_FULLSCREEN);
-    bottomActionView
-        .getRootView()
-        .setSystemUiVisibility(
-            View.SYSTEM_UI_FLAG_LAYOUT_STABLE
-                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
-                | View.SYSTEM_UI_FLAG_FULLSCREEN);
-
-    // Shows the botton action view
-    bottomActionView
-        .animate()
-        .alpha(1f)
-        .setInterpolator(new LinearInterpolator())
-        .setStartDelay(SHOW_TARGET_DELAY)
-        .setDuration(SHOW_HIDE_TARGET_DURATION)
-        .start();
-  }
-
-  /** Hides and destroys the bottom action view. */
-  public void destroyBottomActionView() {
-    if (bottomActionView == null) {
-      return;
-    }
-    bottomActionView
-        .animate()
-        .alpha(0f)
-        .setInterpolator(new LinearInterpolator())
-        .setDuration(SHOW_HIDE_TARGET_DURATION)
-        .withEndAction(
-            () -> {
-              // Use removeViewImmediate instead of removeView to avoid view flashing before removed
-              windowManager.removeViewImmediate(bottomActionView);
-              bottomActionView = null;
-            })
-        .start();
-  }
-
-  /**
-   * Change highlight state of dismiss view and end call view according to current touch point.
-   * Highlight the view with touch point moving into its boundary. Unhighlight the view with touch
-   * point moving out of its boundary.
-   *
-   * @param x x position of current touch point
-   * @param y y position of current touch point
-   */
-  public void highlightIfHover(float x, float y) {
-    if (bottomActionView == null) {
-      return;
-    }
-    final int middle = context.getResources().getDisplayMetrics().widthPixels / 2;
-    boolean shouldHighlightDismiss = y > bottomActionViewTop && x < middle;
-    boolean shouldHighlightEndCall = y > bottomActionViewTop && x >= middle;
-
-    // Set target alpha back to 1
-    if (!dismissHighlighted && endCallHighlighted && !shouldHighlightEndCall) {
-      dismissView.animate().alpha(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start();
-    }
-    if (!endCallHighlighted && dismissHighlighted && !shouldHighlightDismiss) {
-      endCallView.animate().alpha(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start();
-    }
-
-    // Scale unhighlight target back to 1x
-    if (!shouldHighlightDismiss && dismissHighlighted) {
-      // A11y
-      dismissView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
-      // Unhighlight dismiss
-      dismissView.animate().scaleX(1f).scaleY(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start();
-      dismissHighlighted = false;
-    } else if (!shouldHighlightEndCall && endCallHighlighted) {
-      // A11y
-      endCallView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
-      // Unhighlight end call
-      endCallView.animate().scaleX(1f).scaleY(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start();
-      endCallHighlighted = false;
-    }
-
-    // Scale highlight target larger
-    if (shouldHighlightDismiss && !dismissHighlighted) {
-      // A11y
-      dismissView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
-      // Highlight dismiss
-      dismissView.setPivotY(dismissView.getHeight() / 2 + textOffsetSize);
-      dismissView
-          .animate()
-          .scaleX(HIGHLIGHT_TARGET_SCALE)
-          .scaleY(HIGHLIGHT_TARGET_SCALE)
-          .setDuration(HIGHLIGHT_TARGET_DURATION)
-          .start();
-      // Fade the other target
-      endCallView
-          .animate()
-          .alpha(UNHIGHLIGHT_TARGET_ALPHA)
-          .setDuration(HIGHLIGHT_TARGET_DURATION)
-          .start();
-      dismissHighlighted = true;
-    } else if (shouldHighlightEndCall && !endCallHighlighted) {
-      // A11y
-      endCallView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
-      // Highlight end call
-      endCallView.setPivotY(dismissView.getHeight() / 2 + textOffsetSize);
-      endCallView
-          .animate()
-          .scaleX(HIGHLIGHT_TARGET_SCALE)
-          .scaleY(HIGHLIGHT_TARGET_SCALE)
-          .setDuration(HIGHLIGHT_TARGET_DURATION)
-          .start();
-      // Fade the other target
-      dismissView
-          .animate()
-          .alpha(UNHIGHLIGHT_TARGET_ALPHA)
-          .setDuration(HIGHLIGHT_TARGET_DURATION)
-          .start();
-      endCallHighlighted = true;
-    }
-  }
-
-  public boolean isDismissHighlighted() {
-    return dismissHighlighted;
-  }
-
-  public boolean isEndCallHighlighted() {
-    return endCallHighlighted;
-  }
-}
diff --git a/java/com/android/newbubble/NewBubble.java b/java/com/android/newbubble/NewBubble.java
index 54e56ba..8f1be64 100644
--- a/java/com/android/newbubble/NewBubble.java
+++ b/java/com/android/newbubble/NewBubble.java
@@ -16,521 +16,60 @@
 
 package com.android.newbubble;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.annotation.SuppressLint;
-import android.app.PendingIntent.CanceledException;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.Animatable;
 import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Handler;
-import android.provider.Settings;
-import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-import android.support.v4.os.BuildCompat;
-import android.support.v4.view.animation.LinearOutSlowInInterpolator;
-import android.text.TextUtils;
-import android.view.ContextThemeWrapper;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.AccessibilityDelegate;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver.OnPreDrawListener;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.AnticipateInterpolator;
-import android.view.animation.OvershootInterpolator;
-import android.widget.ImageView;
-import android.widget.Toast;
-import com.android.dialer.common.LogUtil;
-import com.android.dialer.logging.DialerImpression;
-import com.android.dialer.logging.Logger;
-import com.android.dialer.util.DrawableConverter;
-import com.android.incallui.call.CallList;
-import com.android.incallui.call.DialerCall;
 import com.android.newbubble.NewBubbleInfo.Action;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.List;
-import java.util.Locale;
 
 /**
  * Creates and manages a bubble window from information in a {@link NewBubbleInfo}. Before creating,
- * be sure to check whether bubbles may be shown using {@link #canShowBubbles(Context)} and request
- * permission if necessary ({@link #getRequestPermissionIntent(Context)} is provided for
- * convenience)
+ * be sure to check whether bubbles may be shown using {@code Settings.canDrawOverlays(context)} and
+ * request permission if necessary
  */
-public class NewBubble {
-  // This class has some odd behavior that is not immediately obvious in order to avoid jank when
-  // resizing. See http://go/bubble-resize for details.
-
-  // How long the new window should show before destroying the old one during resize operations.
-  // This ensures the new window has had time to draw first.
-  private static final int WINDOW_REDRAW_DELAY_MILLIS = 50;
-
-  private static final int EXPAND_AND_COLLAPSE_ANIMATION_DURATION = 200;
-  private static final int HIDE_BUBBLE_ANIMATION_DURATION = 250;
-
-  private static Boolean canShowBubblesForTesting = null;
-
-  private final AccelerateDecelerateInterpolator accelerateDecelerateInterpolator =
-      new AccelerateDecelerateInterpolator();
-
-  private final Context context;
-  private final WindowManager windowManager;
-
-  private final Handler handler;
-  private LayoutParams windowParams;
-
-  // Initialized in factory method
-  @SuppressWarnings("NullableProblems")
-  @NonNull
-  private NewBubbleInfo currentInfo;
-
-  @VisibleForTesting @Visibility int visibility;
-  private boolean expanded;
-  private CharSequence textAfterShow;
-  private int collapseEndAction;
-
-  ViewHolder viewHolder;
-  private AnimatorSet collapseAnimatorSet;
-  private Integer overrideGravity;
-  @VisibleForTesting AnimatorSet exitAnimatorSet;
-  @VisibleForTesting AnimatorSet enterAnimatorSet;
-
-  private int primaryIconMoveDistance;
-  private final int leftBoundary;
-  private int savedYPosition = -1;
-
-  /** Type of action after bubble collapse */
-  @Retention(RetentionPolicy.SOURCE)
-  @IntDef({CollapseEnd.NOTHING, CollapseEnd.HIDE})
-  private @interface CollapseEnd {
-    int NOTHING = 0;
-    int HIDE = 1;
-  }
-
-  @Retention(RetentionPolicy.SOURCE)
-  @VisibleForTesting
-  @IntDef({Visibility.ENTERING, Visibility.SHOWING, Visibility.EXITING, Visibility.HIDDEN})
-  @interface Visibility {
-    int HIDDEN = 0;
-    int ENTERING = 1;
-    int SHOWING = 2;
-    int EXITING = 3;
-  }
-
-  /** Indicate bubble expansion state. */
-  @Retention(RetentionPolicy.SOURCE)
-  @IntDef({ExpansionState.START_EXPANDING, ExpansionState.START_COLLAPSING})
-  public @interface ExpansionState {
-    // TODO(yueg): add more states when needed
-    int START_EXPANDING = 0;
-    int START_COLLAPSING = 1;
-  }
-
-  /**
-   * Determines whether bubbles can be shown based on permissions obtained. This should be checked
-   * before attempting to create a Bubble.
-   *
-   * @return true iff bubbles are able to be shown.
-   * @see Settings#canDrawOverlays(Context)
-   */
-  public static boolean canShowBubbles(@NonNull Context context) {
-    return canShowBubblesForTesting != null
-        ? canShowBubblesForTesting
-        : Settings.canDrawOverlays(context);
-  }
-
-  @VisibleForTesting(otherwise = VisibleForTesting.NONE)
-  public static void setCanShowBubblesForTesting(boolean canShowBubbles) {
-    canShowBubblesForTesting = canShowBubbles;
-  }
-
-  /** Returns an Intent to request permission to show overlays */
-  @NonNull
-  public static Intent getRequestPermissionIntent(@NonNull Context context) {
-    return new Intent(
-        Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
-        Uri.fromParts("package", context.getPackageName(), null));
-  }
-
-  /** Creates instances of Bubble. The default implementation just calls the constructor. */
-  @VisibleForTesting
-  public interface BubbleFactory {
-    NewBubble createBubble(@NonNull Context context, @NonNull Handler handler);
-  }
-
-  private static BubbleFactory bubbleFactory = NewBubble::new;
-
-  public static NewBubble createBubble(@NonNull Context context, @NonNull NewBubbleInfo info) {
-    NewBubble bubble = bubbleFactory.createBubble(context, new Handler());
-    bubble.setBubbleInfo(info);
-    return bubble;
-  }
-
-  @VisibleForTesting
-  public static void setBubbleFactory(@NonNull BubbleFactory bubbleFactory) {
-    NewBubble.bubbleFactory = bubbleFactory;
-  }
-
-  @VisibleForTesting
-  public static void resetBubbleFactory() {
-    NewBubble.bubbleFactory = NewBubble::new;
-  }
-
-  @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-  NewBubble(@NonNull Context context, @NonNull Handler handler) {
-    context = new ContextThemeWrapper(context, R.style.Theme_AppCompat);
-    this.context = context;
-    this.handler = handler;
-    windowManager = context.getSystemService(WindowManager.class);
-
-    viewHolder = new ViewHolder(context);
-
-    leftBoundary =
-        context.getResources().getDimensionPixelOffset(R.dimen.bubble_off_screen_size_horizontal)
-            - context
-                .getResources()
-                .getDimensionPixelSize(R.dimen.bubble_shadow_padding_size_horizontal);
-    primaryIconMoveDistance =
-        context.getResources().getDimensionPixelSize(R.dimen.bubble_size)
-            - context.getResources().getDimensionPixelSize(R.dimen.bubble_small_icon_size);
-  }
-
-  /** Expands the main bubble menu. */
-  public void expand() {
-    setPrimaryButtonAccessibilityAction(
-        context.getString(R.string.a11y_bubble_primary_button_collapse_action));
-
-    viewHolder.setDrawerVisibility(View.INVISIBLE);
-    viewHolder.getArrow().setVisibility(View.INVISIBLE);
-    // No click during animation to avoid jank.
-    viewHolder.setPrimaryButtonClickable(false);
-
-    View expandedView = viewHolder.getExpandedView();
-    expandedView
-        .getViewTreeObserver()
-        .addOnPreDrawListener(
-            new OnPreDrawListener() {
-              @Override
-              public boolean onPreDraw() {
-                // Move the whole bubble up so that expanded view is still in screen
-                int moveUpDistance = viewHolder.getMoveUpDistance();
-                if (moveUpDistance != 0) {
-                  savedYPosition = windowParams.y;
-                }
-
-                // Animation 1: animate x-move and y-move (if needed) together
-                int deltaX =
-                    (int) viewHolder.getRoot().findViewById(R.id.bubble_primary_container).getX();
-                float k = -(float) moveUpDistance / deltaX;
-                if (isDrawingFromRight()) {
-                  deltaX = -deltaX;
-                }
-                ValueAnimator xValueAnimator =
-                    createBubbleMoveAnimator(
-                        windowParams.x - deltaX, windowParams.x, windowParams.y, k);
-
-                // Show expanded view
-                expandedView.setVisibility(View.VISIBLE);
-
-                // Animator 2: reveal expanded view from top left or top right
-                View expandedMenu = viewHolder.getRoot().findViewById(R.id.bubble_expanded_menu);
-                ValueAnimator revealAnim =
-                    createOpenCloseOutlineProvider(expandedMenu)
-                        .createRevealAnimator(expandedMenu, false);
-                revealAnim.setInterpolator(accelerateDecelerateInterpolator);
-
-                // Animator 3: expanded view fade in
-                Animator fadeIn = ObjectAnimator.ofFloat(expandedView, "alpha", 0, 1);
-                fadeIn.setInterpolator(accelerateDecelerateInterpolator);
-
-                // Play all animation together
-                AnimatorSet expandAnimatorSet = new AnimatorSet();
-                expandAnimatorSet.playTogether(revealAnim, fadeIn, xValueAnimator);
-                expandAnimatorSet.setDuration(EXPAND_AND_COLLAPSE_ANIMATION_DURATION);
-                expandAnimatorSet.addListener(
-                    new AnimatorListenerAdapter() {
-                      @Override
-                      public void onAnimationEnd(Animator animation) {
-                        // Show arrow after animation
-                        viewHolder.getArrow().setVisibility(View.VISIBLE);
-                        // Safe to click primary button now
-                        viewHolder.setPrimaryButtonClickable(true);
-                      }
-                    });
-                expandAnimatorSet.start();
-
-                expandedView.getViewTreeObserver().removeOnPreDrawListener(this);
-                return false;
-              }
-            });
-    setFocused(true);
-    expanded = true;
-  }
-
-  @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-  public void startCollapse(@CollapseEnd int endAction, boolean shouldRecoverYPosition) {
-    View expandedView = viewHolder.getExpandedView();
-    if (expandedView.getVisibility() != View.VISIBLE || collapseAnimatorSet != null) {
-      // Drawer is already collapsed or animation is running.
-      return;
-    }
-
-    overrideGravity = isDrawingFromRight() ? Gravity.RIGHT : Gravity.LEFT;
-    setFocused(false);
-
-    if (collapseEndAction == CollapseEnd.NOTHING) {
-      collapseEndAction = endAction;
-    }
-    setPrimaryButtonAccessibilityAction(
-        context.getString(R.string.a11y_bubble_primary_button_expand_action));
-
-    // Hide arrow before animation
-    viewHolder.getArrow().setVisibility(View.INVISIBLE);
-
-    // No click during animation to avoid jank.
-    viewHolder.setPrimaryButtonClickable(false);
-
-    // Calculate animation values
-    int deltaX = (int) viewHolder.getRoot().findViewById(R.id.bubble_primary_container).getX();
-    float k =
-        (savedYPosition != -1 && shouldRecoverYPosition)
-            ? (savedYPosition - windowParams.y) / (float) deltaX
-            : 0;
-    // The position is not useful after collapse
-    savedYPosition = -1;
-
-    // Animation 1: animate x-move and y-move (if needed) together
-    ValueAnimator xValueAnimator =
-        createBubbleMoveAnimator(windowParams.x, windowParams.x - deltaX, windowParams.y, k);
-
-    // Animator 2: hide expanded view to top left or top right
-    View expandedMenu = viewHolder.getRoot().findViewById(R.id.bubble_expanded_menu);
-    ValueAnimator revealAnim =
-        createOpenCloseOutlineProvider(expandedMenu).createRevealAnimator(expandedMenu, true);
-    revealAnim.setInterpolator(accelerateDecelerateInterpolator);
-
-    // Animator 3: expanded view fade out
-    Animator fadeOut = ObjectAnimator.ofFloat(expandedView, "alpha", 1, 0);
-    fadeOut.setInterpolator(accelerateDecelerateInterpolator);
-
-    // Play all animation together
-    collapseAnimatorSet = new AnimatorSet();
-    collapseAnimatorSet.setDuration(EXPAND_AND_COLLAPSE_ANIMATION_DURATION);
-    collapseAnimatorSet.playTogether(revealAnim, fadeOut, xValueAnimator);
-    collapseAnimatorSet.addListener(
-        new AnimatorListenerAdapter() {
-          @Override
-          public void onAnimationEnd(Animator animation) {
-            collapseAnimatorSet = null;
-            expanded = false;
-
-            // If collapse on the right side, the primary button move left a bit after drawer
-            // visibility becoming GONE. To avoid it, we create a new ViewHolder.
-            // It also set primary button clickable back to true, so no need to reset manually.
-            replaceViewHolder();
-
-            // If this collapse was to come before a hide, do it now.
-            if (collapseEndAction == CollapseEnd.HIDE) {
-              hide();
-              collapseEndAction = CollapseEnd.NOTHING;
-            }
-
-            // Resume normal gravity after any resizing is done.
-            handler.postDelayed(
-                () -> {
-                  overrideGravity = null;
-                  if (!viewHolder.isMoving()) {
-                    viewHolder.undoGravityOverride();
-                  }
-                },
-                // Need to wait twice as long for resize and layout
-                WINDOW_REDRAW_DELAY_MILLIS * 2);
-          }
-        });
-    collapseAnimatorSet.start();
-  }
+public interface NewBubble {
 
   /**
    * Make the bubble visible. Will show a short entrance animation as it enters. If the bubble is
    * already showing this method does nothing.
    */
-  public void show() {
-    if (collapseEndAction == CollapseEnd.HIDE) {
-      // If show() was called while collapsing, make sure we don't hide after.
-      collapseEndAction = CollapseEnd.NOTHING;
-    }
-    if (visibility == Visibility.SHOWING || visibility == Visibility.ENTERING) {
-      return;
-    }
-
-    logBasicOrCallImpression(DialerImpression.Type.BUBBLE_V2_SHOW);
-
-    boolean isRtl =
-        TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL;
-    if (windowParams == null) {
-      // Apps targeting O+ must use TYPE_APPLICATION_OVERLAY, which is not available prior to O.
-      @SuppressWarnings("deprecation")
-      @SuppressLint("InlinedApi")
-      int type =
-          BuildCompat.isAtLeastO()
-              ? LayoutParams.TYPE_APPLICATION_OVERLAY
-              : LayoutParams.TYPE_PHONE;
-
-      windowParams =
-          new LayoutParams(
-              type,
-              LayoutParams.FLAG_NOT_TOUCH_MODAL
-                  | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-                  | LayoutParams.FLAG_NOT_FOCUSABLE
-                  | LayoutParams.FLAG_LAYOUT_NO_LIMITS,
-              PixelFormat.TRANSLUCENT);
-      windowParams.gravity = Gravity.TOP | (isRtl ? Gravity.RIGHT : Gravity.LEFT);
-      windowParams.x = leftBoundary;
-      windowParams.y = currentInfo.getStartingYPosition();
-      windowParams.height = LayoutParams.WRAP_CONTENT;
-      windowParams.width = LayoutParams.WRAP_CONTENT;
-    }
-
-    if (exitAnimatorSet != null) {
-      exitAnimatorSet.removeAllListeners();
-      exitAnimatorSet.cancel();
-      exitAnimatorSet = null;
-    } else {
-      windowManager.addView(viewHolder.getRoot(), windowParams);
-      viewHolder.getPrimaryButton().setVisibility(View.VISIBLE);
-      viewHolder.getPrimaryButton().setScaleX(0);
-      viewHolder.getPrimaryButton().setScaleY(0);
-      viewHolder.getPrimaryAvatar().setAlpha(0f);
-      viewHolder.getPrimaryIcon().setAlpha(0f);
-      if (isRtl) {
-        onLeftRightSwitch(true);
-      }
-    }
-
-    viewHolder.setChildClickable(true);
-    visibility = Visibility.ENTERING;
-
-    setPrimaryButtonAccessibilityAction(
-        context.getString(R.string.a11y_bubble_primary_button_expand_action));
-
-    // Show bubble animation: scale the whole bubble to 1, and change avatar+icon's alpha to 1
-    ObjectAnimator scaleXAnimator =
-        ObjectAnimator.ofFloat(viewHolder.getPrimaryButton(), "scaleX", 1);
-    ObjectAnimator scaleYAnimator =
-        ObjectAnimator.ofFloat(viewHolder.getPrimaryButton(), "scaleY", 1);
-    ObjectAnimator avatarAlphaAnimator =
-        ObjectAnimator.ofFloat(viewHolder.getPrimaryAvatar(), "alpha", 1);
-    ObjectAnimator iconAlphaAnimator =
-        ObjectAnimator.ofFloat(viewHolder.getPrimaryIcon(), "alpha", 1);
-    enterAnimatorSet = new AnimatorSet();
-    enterAnimatorSet.playTogether(
-        scaleXAnimator, scaleYAnimator, avatarAlphaAnimator, iconAlphaAnimator);
-    enterAnimatorSet.setInterpolator(new OvershootInterpolator());
-    enterAnimatorSet.addListener(
-        new AnimatorListenerAdapter() {
-          @Override
-          public void onAnimationEnd(Animator animation) {
-            visibility = Visibility.SHOWING;
-            // Show the queued up text, if available.
-            if (textAfterShow != null) {
-              showText(textAfterShow);
-              textAfterShow = null;
-            }
-          }
-        });
-    enterAnimatorSet.start();
-
-    updatePrimaryIconAnimation();
-  }
+  void show();
 
   /** Hide the bubble. */
-  public void hide() {
-    hideHelper(this::defaultAfterHidingAnimation);
-  }
+  void hide();
 
-  /** Hide the bubble and reset {@viewHolder} to initial state */
-  public void hideAndReset() {
-    hideHelper(
-        () -> {
-          defaultAfterHidingAnimation();
-          reset();
-        });
-  }
+  /** Hide the bubble and reset to initial state */
+  void hideAndReset();
 
   /** Returns whether the bubble is currently visible */
-  public boolean isVisible() {
-    return visibility == Visibility.SHOWING
-        || visibility == Visibility.ENTERING
-        || visibility == Visibility.EXITING;
-  }
+  boolean isVisible();
 
   /**
    * Set the info for this Bubble to display
    *
    * @param bubbleInfo the BubbleInfo to display in this Bubble.
    */
-  public void setBubbleInfo(@NonNull NewBubbleInfo bubbleInfo) {
-    currentInfo = bubbleInfo;
-    update();
-  }
+  void setBubbleInfo(@NonNull NewBubbleInfo bubbleInfo);
 
   /**
    * Update the state and behavior of actions.
    *
    * @param actions the new state of the bubble's actions
    */
-  public void updateActions(@NonNull List<Action> actions) {
-    currentInfo = NewBubbleInfo.from(currentInfo).setActions(actions).build();
-    updateButtonStates();
-  }
+  void updateActions(@NonNull List<Action> actions);
 
   /**
    * Update the avatar from photo.
    *
    * @param avatar the new photo avatar in the bubble's primary button
    */
-  public void updatePhotoAvatar(@NonNull Drawable avatar) {
-    // Make it round
-    int bubbleSize = context.getResources().getDimensionPixelSize(R.dimen.bubble_size);
-    Drawable roundAvatar =
-        DrawableConverter.getRoundedDrawable(context, avatar, bubbleSize, bubbleSize);
-
-    updateAvatar(roundAvatar);
-  }
+  void updatePhotoAvatar(@NonNull Drawable avatar);
 
   /**
    * Update the avatar.
    *
    * @param avatar the new avatar in the bubble's primary button
    */
-  public void updateAvatar(@NonNull Drawable avatar) {
-    if (!avatar.equals(currentInfo.getAvatar())) {
-      currentInfo = NewBubbleInfo.from(currentInfo).setAvatar(avatar).build();
-      viewHolder.getPrimaryAvatar().setBackground(currentInfo.getAvatar());
-    }
-  }
-
-  /** Returns the currently displayed NewBubbleInfo */
-  public NewBubbleInfo getBubbleInfo() {
-    return currentInfo;
-  }
+  void updateAvatar(@NonNull Drawable avatar);
 
   /**
    * Display text. The bubble's drawer is not expandable while text is showing, and the drawer will
@@ -538,481 +77,5 @@
    *
    * @param text the text to display to the user
    */
-  public void showText(@NonNull CharSequence text) {
-    if (expanded) {
-      startCollapse(CollapseEnd.NOTHING, false /* shouldRecoverYPosition */);
-    }
-    Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
-  }
-
-  @Nullable
-  Integer getGravityOverride() {
-    return overrideGravity;
-  }
-
-  void onMoveStart() {
-    if (viewHolder.getExpandedView().getVisibility() == View.VISIBLE) {
-      viewHolder.setDrawerVisibility(View.INVISIBLE);
-    }
-    expanded = false;
-    savedYPosition = -1;
-
-    viewHolder
-        .getPrimaryAvatar()
-        .animate()
-        .translationZ(
-            context
-                .getResources()
-                .getDimensionPixelOffset(R.dimen.bubble_dragging_elevation_change));
-  }
-
-  void onMoveFinish() {
-    viewHolder.getPrimaryAvatar().animate().translationZ(0);
-  }
-
-  void primaryButtonClick() {
-    if (expanded) {
-      logBasicOrCallImpression(DialerImpression.Type.BUBBLE_V2_CLICK_TO_COLLAPSE);
-      startCollapse(CollapseEnd.NOTHING, true /* shouldRecoverYPosition */);
-    } else {
-      logBasicOrCallImpression(DialerImpression.Type.BUBBLE_V2_CLICK_TO_EXPAND);
-      expand();
-    }
-  }
-
-  void onLeftRightSwitch(boolean onRight) {
-    // Move primary icon to the other side so it's not partially hiden
-    View primaryIcon = viewHolder.getPrimaryIcon();
-    primaryIcon.animate().translationX(onRight ? -primaryIconMoveDistance : 0).start();
-  }
-
-  LayoutParams getWindowParams() {
-    return windowParams;
-  }
-
-  View getRootView() {
-    return viewHolder.getRoot();
-  }
-
-  /**
-   * Hide the bubble if visible. Will run a short exit animation and before hiding, and {@code
-   * afterHiding} after hiding. If the bubble is currently showing text, will hide after the text is
-   * done displaying. If the bubble is not visible this method does nothing.
-   */
-  @VisibleForTesting
-  void hideHelper(Runnable afterHiding) {
-    if (visibility == Visibility.HIDDEN || visibility == Visibility.EXITING) {
-      return;
-    }
-
-    // Make bubble non clickable to prevent further buggy actions
-    viewHolder.setChildClickable(false);
-
-    if (visibility == Visibility.ENTERING) {
-      enterAnimatorSet.removeAllListeners();
-      enterAnimatorSet.cancel();
-      enterAnimatorSet = null;
-      afterHiding.run();
-      return;
-    }
-
-    if (collapseAnimatorSet != null) {
-      collapseEndAction = CollapseEnd.HIDE;
-      return;
-    }
-
-    if (expanded) {
-      startCollapse(CollapseEnd.HIDE, false /* shouldRecoverYPosition */);
-      return;
-    }
-
-    visibility = Visibility.EXITING;
-
-    // Hide bubble animation: scale the whole bubble to 0, and change avatar+icon's alpha to 0
-    ObjectAnimator scaleXAnimator =
-        ObjectAnimator.ofFloat(viewHolder.getPrimaryButton(), "scaleX", 0);
-    ObjectAnimator scaleYAnimator =
-        ObjectAnimator.ofFloat(viewHolder.getPrimaryButton(), "scaleY", 0);
-    ObjectAnimator avatarAlphaAnimator =
-        ObjectAnimator.ofFloat(viewHolder.getPrimaryAvatar(), "alpha", 0);
-    ObjectAnimator iconAlphaAnimator =
-        ObjectAnimator.ofFloat(viewHolder.getPrimaryIcon(), "alpha", 0);
-    exitAnimatorSet = new AnimatorSet();
-    exitAnimatorSet.playTogether(
-        scaleXAnimator, scaleYAnimator, avatarAlphaAnimator, iconAlphaAnimator);
-    exitAnimatorSet.setInterpolator(new AnticipateInterpolator());
-    exitAnimatorSet.setDuration(HIDE_BUBBLE_ANIMATION_DURATION);
-    exitAnimatorSet.addListener(
-        new AnimatorListenerAdapter() {
-          @Override
-          public void onAnimationStart(Animator animation) {
-            viewHolder.getPrimaryButton().setAccessibilityDelegate(null);
-          }
-
-          @Override
-          public void onAnimationEnd(Animator animation) {
-            afterHiding.run();
-          }
-        });
-    exitAnimatorSet.start();
-  }
-
-  private void reset() {
-    viewHolder = new ViewHolder(viewHolder.getRoot().getContext());
-    update();
-  }
-
-  private void update() {
-    // The value may change on display size changed.
-    primaryIconMoveDistance =
-        context.getResources().getDimensionPixelSize(R.dimen.bubble_size)
-            - context.getResources().getDimensionPixelSize(R.dimen.bubble_small_icon_size);
-
-    // Avatar
-    viewHolder.getPrimaryAvatar().setBackground(currentInfo.getAvatar());
-
-    // Small icon
-    Drawable smallIconBackgroundCircle =
-        context
-            .getResources()
-            .getDrawable(R.drawable.bubble_shape_circle_small, context.getTheme());
-    smallIconBackgroundCircle.setTint(context.getColor(R.color.bubble_button_color_blue));
-    viewHolder.getPrimaryIcon().setBackground(smallIconBackgroundCircle);
-    viewHolder.getPrimaryIcon().setImageIcon(currentInfo.getPrimaryIcon());
-
-    updatePrimaryIconAnimation();
-    updateButtonStates();
-  }
-
-  private void updatePrimaryIconAnimation() {
-    Drawable drawable = viewHolder.getPrimaryIcon().getDrawable();
-    if (drawable instanceof Animatable) {
-      if (isVisible()) {
-        ((Animatable) drawable).start();
-      } else {
-        ((Animatable) drawable).stop();
-      }
-    }
-  }
-
-  private void updateButtonStates() {
-    configureButton(currentInfo.getActions().get(0), viewHolder.getFullScreenButton());
-    configureButton(currentInfo.getActions().get(1), viewHolder.getMuteButton());
-    configureButton(currentInfo.getActions().get(2), viewHolder.getAudioRouteButton());
-    configureButton(currentInfo.getActions().get(3), viewHolder.getEndCallButton());
-  }
-
-  private void configureButton(Action action, NewCheckableButton button) {
-    boolean isRtl =
-        TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL;
-    if (isRtl) {
-      button.setCompoundDrawablesWithIntrinsicBounds(null, null, action.getIconDrawable(), null);
-    } else {
-      button.setCompoundDrawablesWithIntrinsicBounds(action.getIconDrawable(), null, null, null);
-    }
-    button.setChecked(action.isChecked());
-    button.setCheckable(action.isCheckable());
-    button.setText(action.getName());
-    button.setContentDescription(action.getName());
-    button.setOnClickListener(v -> doAction(action));
-  }
-
-  private void doAction(Action action) {
-    try {
-      action.getIntent().send();
-    } catch (CanceledException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-  /**
-   * Create a new ViewHolder object to replace the old one.It only happens when not moving and
-   * collapsed.
-   */
-  void replaceViewHolder() {
-    LogUtil.enterBlock("NewBubble.replaceViewHolder");
-    // Don't do it. If windowParams is null, either we haven't initialized it or we set it to null.
-    // There is no need to recreate bubble.
-    if (windowParams == null) {
-      return;
-    }
-
-    ViewHolder oldViewHolder = viewHolder;
-
-    // Create a new ViewHolder and copy needed info.
-    viewHolder = new ViewHolder(oldViewHolder.getRoot().getContext());
-    viewHolder.getPrimaryIcon().setX(isDrawingFromRight() ? 0 : primaryIconMoveDistance);
-    viewHolder
-        .getPrimaryIcon()
-        .setTranslationX(isDrawingFromRight() ? -primaryIconMoveDistance : 0);
-    setPrimaryButtonAccessibilityAction(
-        context.getString(R.string.a11y_bubble_primary_button_expand_action));
-
-    update();
-
-    // Add new view at its horizontal boundary
-    ViewGroup root = viewHolder.getRoot();
-    windowParams.x = leftBoundary;
-    windowParams.gravity = Gravity.TOP | (isDrawingFromRight() ? Gravity.RIGHT : Gravity.LEFT);
-    windowManager.addView(root, windowParams);
-
-    // Remove the old view after delay
-    root.getViewTreeObserver()
-        .addOnPreDrawListener(
-            new OnPreDrawListener() {
-              @Override
-              public boolean onPreDraw() {
-                root.getViewTreeObserver().removeOnPreDrawListener(this);
-                // Wait a bit before removing the old view; make sure the new one has drawn over it.
-                handler.postDelayed(
-                    () -> windowManager.removeView(oldViewHolder.getRoot()),
-                    WINDOW_REDRAW_DELAY_MILLIS);
-                return true;
-              }
-            });
-  }
-
-  int getDrawerVisibility() {
-    return viewHolder.getExpandedView().getVisibility();
-  }
-
-  void bottomActionDismiss() {
-    logBasicOrCallImpression(DialerImpression.Type.BUBBLE_V2_BOTTOM_ACTION_DISMISS);
-    // Create bubble at default location at next time
-    hideAndReset();
-    windowParams = null;
-  }
-
-  void bottomActionEndCall() {
-    logBasicOrCallImpression(DialerImpression.Type.BUBBLE_V2_BOTTOM_ACTION_END_CALL);
-    DialerCall call = getCall();
-    if (call != null) {
-      call.disconnect();
-    }
-  }
-
-  private boolean isDrawingFromRight() {
-    return (windowParams.gravity & Gravity.RIGHT) == Gravity.RIGHT;
-  }
-
-  private void setFocused(boolean focused) {
-    if (focused) {
-      windowParams.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
-    } else {
-      windowParams.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
-    }
-    windowManager.updateViewLayout(getRootView(), windowParams);
-  }
-
-  private void defaultAfterHidingAnimation() {
-    exitAnimatorSet = null;
-    viewHolder.getPrimaryButton().setVisibility(View.INVISIBLE);
-    windowManager.removeView(viewHolder.getRoot());
-    visibility = Visibility.HIDDEN;
-
-    updatePrimaryIconAnimation();
-  }
-
-  private void logBasicOrCallImpression(DialerImpression.Type impressionType) {
-    DialerCall call = getCall();
-    if (call != null) {
-      Logger.get(context)
-          .logCallImpression(impressionType, call.getUniqueCallId(), call.getTimeAddedMs());
-    } else {
-      Logger.get(context).logImpression(impressionType);
-    }
-  }
-
-  private DialerCall getCall() {
-    // Bubble is shown for outgoing, active or background call
-    DialerCall call = CallList.getInstance().getOutgoingCall();
-    if (call == null) {
-      call = CallList.getInstance().getActiveOrBackgroundCall();
-    }
-    return call;
-  }
-
-  private void setPrimaryButtonAccessibilityAction(String description) {
-    viewHolder
-        .getPrimaryButton()
-        .setAccessibilityDelegate(
-            new AccessibilityDelegate() {
-              @Override
-              public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) {
-                super.onInitializeAccessibilityNodeInfo(v, info);
-
-                AccessibilityAction clickAction =
-                    new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, description);
-                info.addAction(clickAction);
-              }
-            });
-  }
-
-  private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider(View view) {
-    int startRectX = isDrawingFromRight() ? view.getMeasuredWidth() : 0;
-    Rect startRect = new Rect(startRectX, 0, startRectX, 0);
-    Rect endRect = new Rect(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
-
-    float bubbleRadius = context.getResources().getDimension(R.dimen.bubble_radius);
-    return new RoundedRectRevealOutlineProvider(bubbleRadius, bubbleRadius, startRect, endRect);
-  }
-
-  private ValueAnimator createBubbleMoveAnimator(int startX, int endX, int startY, float k) {
-    ValueAnimator xValueAnimator = ValueAnimator.ofFloat(startX, endX);
-    xValueAnimator.setInterpolator(new LinearOutSlowInInterpolator());
-    xValueAnimator.addUpdateListener(
-        (valueAnimator) -> {
-          if (windowParams == null) {
-            return;
-          }
-          // Update windowParams and the root layout.
-          // We can't do ViewPropertyAnimation since it clips children.
-          float newX = (float) valueAnimator.getAnimatedValue();
-          if (k != 0) {
-            windowParams.y = startY + (int) (Math.abs(newX - (float) startX) * k);
-          }
-          windowParams.x = (int) newX;
-          windowManager.updateViewLayout(viewHolder.getRoot(), windowParams);
-        });
-    return xValueAnimator;
-  }
-
-  @VisibleForTesting
-  class ViewHolder {
-
-    private NewMoveHandler moveHandler;
-    private final NewWindowRoot root;
-    private final View primaryButton;
-    private final ImageView primaryIcon;
-    private final ImageView primaryAvatar;
-    private final View arrow;
-
-    private final NewCheckableButton fullScreenButton;
-    private final NewCheckableButton muteButton;
-    private final NewCheckableButton audioRouteButton;
-    private final NewCheckableButton endCallButton;
-    private final View expandedView;
-
-    public ViewHolder(Context context) {
-      // Window root is not in the layout file so that the inflater has a view to inflate into
-      this.root = new NewWindowRoot(context);
-      LayoutInflater inflater = LayoutInflater.from(root.getContext());
-      View contentView = inflater.inflate(R.layout.new_bubble_base, root, true);
-      expandedView = contentView.findViewById(R.id.bubble_expanded_layout);
-      primaryButton = contentView.findViewById(R.id.bubble_button_primary);
-      primaryAvatar = contentView.findViewById(R.id.bubble_icon_avatar);
-      primaryIcon = contentView.findViewById(R.id.bubble_icon_primary);
-      arrow = contentView.findViewById(R.id.bubble_triangle);
-
-      fullScreenButton = contentView.findViewById(R.id.bubble_button_full_screen);
-      muteButton = contentView.findViewById(R.id.bubble_button_mute);
-      audioRouteButton = contentView.findViewById(R.id.bubble_button_audio_route);
-      endCallButton = contentView.findViewById(R.id.bubble_button_end_call);
-
-      root.setOnBackPressedListener(
-          () -> {
-            if (visibility == Visibility.SHOWING && expanded) {
-              logBasicOrCallImpression(DialerImpression.Type.BUBBLE_V2_CLICK_TO_COLLAPSE);
-              startCollapse(CollapseEnd.NOTHING, true /* shouldRecoverYPosition */);
-              return true;
-            }
-            return false;
-          });
-      root.setOnConfigurationChangedListener(
-          (configuration) -> {
-            if (expanded) {
-              startCollapse(CollapseEnd.NOTHING, false /* shouldRecoverYPosition */);
-            }
-            // The values in the current MoveHandler may be stale, so replace it. Then ensure the
-            // Window is in bounds, and redraw the changes
-            moveHandler = new NewMoveHandler(primaryButton, NewBubble.this);
-            moveHandler.snapToBounds();
-            replaceViewHolder();
-          });
-      root.setOnTouchListener(
-          (v, event) -> {
-            if (expanded && event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) {
-              logBasicOrCallImpression(DialerImpression.Type.BUBBLE_V2_CLICK_TO_COLLAPSE);
-              startCollapse(CollapseEnd.NOTHING, true /* shouldRecoverYPosition */);
-              return true;
-            }
-            return false;
-          });
-      moveHandler = new NewMoveHandler(primaryButton, NewBubble.this);
-    }
-
-    private void setChildClickable(boolean clickable) {
-      fullScreenButton.setClickable(clickable);
-      muteButton.setClickable(clickable);
-      audioRouteButton.setClickable(clickable);
-      endCallButton.setClickable(clickable);
-      setPrimaryButtonClickable(clickable);
-    }
-
-    private void setPrimaryButtonClickable(boolean clickable) {
-      moveHandler.setClickable(clickable);
-    }
-
-    public int getMoveUpDistance() {
-      int deltaAllowed =
-          expandedView.getHeight()
-              - context
-                      .getResources()
-                      .getDimensionPixelOffset(R.dimen.bubble_button_padding_vertical)
-                  * 2;
-      return moveHandler.getMoveUpDistance(deltaAllowed);
-    }
-
-    public ViewGroup getRoot() {
-      return root;
-    }
-
-    public View getPrimaryButton() {
-      return primaryButton;
-    }
-
-    public ImageView getPrimaryIcon() {
-      return primaryIcon;
-    }
-
-    public ImageView getPrimaryAvatar() {
-      return primaryAvatar;
-    }
-
-    public View getExpandedView() {
-      return expandedView;
-    }
-
-    public View getArrow() {
-      return arrow;
-    }
-
-    public NewCheckableButton getFullScreenButton() {
-      return fullScreenButton;
-    }
-
-    public NewCheckableButton getMuteButton() {
-      return muteButton;
-    }
-
-    public NewCheckableButton getAudioRouteButton() {
-      return audioRouteButton;
-    }
-
-    public NewCheckableButton getEndCallButton() {
-      return endCallButton;
-    }
-
-    public void setDrawerVisibility(int visibility) {
-      expandedView.setVisibility(visibility);
-    }
-
-    public boolean isMoving() {
-      return moveHandler.isMoving();
-    }
-
-    public void undoGravityOverride() {
-      moveHandler.undoGravityOverride();
-    }
-  }
+  void showText(@NonNull CharSequence text);
 }
diff --git a/java/com/android/newbubble/NewBubbleComponent.java b/java/com/android/newbubble/NewBubbleComponent.java
new file mode 100644
index 0000000..e7edba6
--- /dev/null
+++ b/java/com/android/newbubble/NewBubbleComponent.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 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.newbubble;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import com.android.dialer.inject.HasRootComponent;
+import dagger.Subcomponent;
+
+@Subcomponent
+public abstract class NewBubbleComponent {
+
+  @NonNull
+  public abstract NewBubble getNewBubble();
+
+  public static NewBubbleComponent get(Context context) {
+    return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component())
+        .newBubbleComponent();
+  }
+
+  /** Used to refer to the root application component. */
+  public interface HasComponent {
+    NewBubbleComponent newBubbleComponent();
+  }
+}
diff --git a/java/com/android/newbubble/NewChangeOnScreenBounds.java b/java/com/android/newbubble/NewChangeOnScreenBounds.java
deleted file mode 100644
index 0653d3a..0000000
--- a/java/com/android/newbubble/NewChangeOnScreenBounds.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright (C) 2017 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.newbubble;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.graphics.Path;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.support.annotation.VisibleForTesting;
-import android.transition.Transition;
-import android.transition.TransitionValues;
-import android.util.Property;
-import android.view.View;
-import android.view.ViewGroup;
-
-/** Similar to {@link android.transition.ChangeBounds ChangeBounds} but works across windows */
-public class NewChangeOnScreenBounds extends Transition {
-
-  @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-  static final String PROPNAME_BOUNDS = "bubble:changeScreenBounds:bounds";
-
-  @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-  static final String PROPNAME_SCREEN_X = "bubble:changeScreenBounds:screenX";
-
-  @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-  static final String PROPNAME_SCREEN_Y = "bubble:changeScreenBounds:screenY";
-
-  static final String PROPNAME_WIDTH = "bubble:changeScreenBounds:width";
-  static final String PROPNAME_HEIGHT = "bubble:changeScreenBounds:height";
-
-  private static final Property<ViewBounds, PointF> TOP_LEFT_PROPERTY =
-      new Property<ViewBounds, PointF>(PointF.class, "topLeft") {
-        @Override
-        public void set(ViewBounds viewBounds, PointF topLeft) {
-          viewBounds.setTopLeft(topLeft);
-        }
-
-        @Override
-        public PointF get(ViewBounds viewBounds) {
-          return null;
-        }
-      };
-
-  private static final Property<ViewBounds, PointF> BOTTOM_RIGHT_PROPERTY =
-      new Property<ViewBounds, PointF>(PointF.class, "bottomRight") {
-        @Override
-        public void set(ViewBounds viewBounds, PointF bottomRight) {
-          viewBounds.setBottomRight(bottomRight);
-        }
-
-        @Override
-        public PointF get(ViewBounds viewBounds) {
-          return null;
-        }
-      };
-  private final int[] tempLocation = new int[2];
-
-  @Override
-  public void captureStartValues(TransitionValues transitionValues) {
-    captureValuesWithSize(transitionValues);
-  }
-
-  @Override
-  public void captureEndValues(TransitionValues transitionValues) {
-    captureValuesWithSize(transitionValues);
-  }
-
-  /**
-   * Capture location (left and top) from {@code values.view} and size (width and height) from
-   * {@code values.values}. If size is not set, use the size of {@code values.view}.
-   */
-  private void captureValuesWithSize(TransitionValues values) {
-    View view = values.view;
-
-    if (view.isLaidOut() || view.getWidth() != 0 || view.getHeight() != 0) {
-      Integer width = (Integer) values.values.get(PROPNAME_WIDTH);
-      Integer height = (Integer) values.values.get(PROPNAME_HEIGHT);
-
-      values.values.put(
-          PROPNAME_BOUNDS,
-          new Rect(
-              view.getLeft(),
-              view.getTop(),
-              width == null ? view.getRight() : view.getLeft() + width,
-              height == null ? view.getBottom() : view.getTop() + height));
-      values.view.getLocationOnScreen(tempLocation);
-      values.values.put(PROPNAME_SCREEN_X, tempLocation[0]);
-      values.values.put(PROPNAME_SCREEN_Y, tempLocation[1]);
-    }
-  }
-
-  @Override
-  public Animator createAnimator(
-      ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
-    Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
-    Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
-
-    if (startBounds == null || endBounds == null) {
-      // start or end values were not captured, so don't animate.
-      return null;
-    }
-
-    // Offset the startBounds by the difference in screen position
-    int startScreenX = (Integer) startValues.values.get(PROPNAME_SCREEN_X);
-    int startScreenY = (Integer) startValues.values.get(PROPNAME_SCREEN_Y);
-    int endScreenX = (Integer) endValues.values.get(PROPNAME_SCREEN_X);
-    int endScreenY = (Integer) endValues.values.get(PROPNAME_SCREEN_Y);
-    startBounds.offset(startScreenX - endScreenX, startScreenY - endScreenY);
-
-    final int startLeft = startBounds.left;
-    final int endLeft = endBounds.left;
-    final int startTop = startBounds.top;
-    final int endTop = endBounds.top;
-    final int startRight = startBounds.right;
-    final int endRight = endBounds.right;
-    final int startBottom = startBounds.bottom;
-    final int endBottom = endBounds.bottom;
-    ViewBounds viewBounds = new ViewBounds(endValues.view);
-    viewBounds.setTopLeft(new PointF(startLeft, startTop));
-    viewBounds.setBottomRight(new PointF(startRight, startBottom));
-
-    // Animate the top left and bottom right corners along a path
-    Path topLeftPath = getPathMotion().getPath(startLeft, startTop, endLeft, endTop);
-    ObjectAnimator topLeftAnimator =
-        ObjectAnimator.ofObject(viewBounds, TOP_LEFT_PROPERTY, null, topLeftPath);
-
-    Path bottomRightPath = getPathMotion().getPath(startRight, startBottom, endRight, endBottom);
-    ObjectAnimator bottomRightAnimator =
-        ObjectAnimator.ofObject(viewBounds, BOTTOM_RIGHT_PROPERTY, null, bottomRightPath);
-    AnimatorSet set = new AnimatorSet();
-    set.playTogether(topLeftAnimator, bottomRightAnimator);
-    return set;
-  }
-
-  private static class ViewBounds {
-    private int left;
-    private int top;
-    private int right;
-    private int bottom;
-    private final View view;
-    private int topLeftCalls;
-    private int bottomRightCalls;
-
-    public ViewBounds(View view) {
-      this.view = view;
-    }
-
-    public void setTopLeft(PointF topLeft) {
-      left = Math.round(topLeft.x);
-      top = Math.round(topLeft.y);
-      topLeftCalls++;
-      if (topLeftCalls == bottomRightCalls) {
-        updateLeftTopRightBottom();
-      }
-    }
-
-    public void setBottomRight(PointF bottomRight) {
-      right = Math.round(bottomRight.x);
-      bottom = Math.round(bottomRight.y);
-      bottomRightCalls++;
-      if (topLeftCalls == bottomRightCalls) {
-        updateLeftTopRightBottom();
-      }
-    }
-
-    private void updateLeftTopRightBottom() {
-      view.setLeft(left);
-      view.setTop(top);
-      view.setRight(right);
-      view.setBottom(bottom);
-      topLeftCalls = 0;
-      bottomRightCalls = 0;
-    }
-  }
-}
diff --git a/java/com/android/newbubble/NewCheckableButton.java b/java/com/android/newbubble/NewCheckableButton.java
deleted file mode 100644
index fda0ddc..0000000
--- a/java/com/android/newbubble/NewCheckableButton.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2017 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.newbubble;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.support.v4.view.AccessibilityDelegateCompat;
-import android.support.v4.view.ViewCompat;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.support.v7.widget.AppCompatButton;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.widget.Checkable;
-
-/**
- * A {@link android.widget.Button Button} that implements {@link Checkable} and propagates the
- * checkable state
- */
-public class NewCheckableButton extends AppCompatButton implements Checkable {
-
-  private boolean checked;
-
-  public NewCheckableButton(Context context) {
-    this(context, null);
-  }
-
-  public NewCheckableButton(Context context, AttributeSet attrs) {
-    this(context, attrs, android.R.attr.imageButtonStyle);
-  }
-
-  public NewCheckableButton(Context context, AttributeSet attrs, int defStyleAttr) {
-    super(context, attrs, defStyleAttr);
-  }
-
-  public void setCheckable(boolean checkable) {
-    ViewCompat.setAccessibilityDelegate(
-        this,
-        new AccessibilityDelegateCompat() {
-          @Override
-          public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
-            super.onInitializeAccessibilityEvent(host, event);
-            if (checkable) {
-              event.setChecked(isChecked());
-            }
-          }
-
-          @Override
-          public void onInitializeAccessibilityNodeInfo(
-              View host, AccessibilityNodeInfoCompat info) {
-            super.onInitializeAccessibilityNodeInfo(host, info);
-            info.setCheckable(checkable);
-            if (checkable) {
-              info.setChecked(isChecked());
-            }
-          }
-        });
-  }
-
-  @Override
-  public void setChecked(boolean checked) {
-    if (this.checked != checked) {
-      this.checked = checked;
-      int newColor =
-          checked
-              ? getContext().getColor(R.color.bubble_button_color_blue)
-              : getContext().getColor(R.color.bubble_button_color_grey);
-      setTextColor(newColor);
-      setCompoundDrawableTintList(ColorStateList.valueOf(newColor));
-    }
-  }
-
-  @Override
-  public boolean isChecked() {
-    return checked;
-  }
-
-  @Override
-  public void toggle() {
-    setChecked(!checked);
-  }
-}
diff --git a/java/com/android/newbubble/NewMoveHandler.java b/java/com/android/newbubble/NewMoveHandler.java
deleted file mode 100644
index 12e099c..0000000
--- a/java/com/android/newbubble/NewMoveHandler.java
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
- * Copyright (C) 2017 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.newbubble;
-
-import android.content.Context;
-import android.graphics.Point;
-import android.support.animation.FloatPropertyCompat;
-import android.support.animation.SpringAnimation;
-import android.support.animation.SpringForce;
-import android.support.annotation.NonNull;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.View.OnTouchListener;
-import android.view.ViewConfiguration;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-import android.widget.Scroller;
-
-/** Handles touches and manages moving the bubble in response */
-class NewMoveHandler implements OnTouchListener {
-
-  // Amount the ViewConfiguration's minFlingVelocity will be scaled by for our own minVelocity
-  private static final int MIN_FLING_VELOCITY_FACTOR = 8;
-  // The friction multiplier to control how slippery the bubble is when flung
-  private static final float SCROLL_FRICTION_MULTIPLIER = 4f;
-
-  private final Context context;
-  private final WindowManager windowManager;
-  private final NewBubble bubble;
-  private final int minX;
-  private final int minY;
-  private final int maxX;
-  private final int maxY;
-  private final int bubbleSize;
-  private final int bubbleShadowPaddingHorizontal;
-  private final int bubbleExpandedViewWidth;
-  private final float touchSlopSquared;
-  private final BottomActionViewController bottomActionViewController;
-
-  private boolean clickable = true;
-  private boolean isMoving;
-  private float firstX;
-  private float firstY;
-
-  private SpringAnimation moveXAnimation;
-  private SpringAnimation moveYAnimation;
-  private VelocityTracker velocityTracker;
-  private Scroller scroller;
-
-  private static float clamp(float value, float min, float max) {
-    return Math.min(max, Math.max(min, value));
-  }
-
-  // Handles the left/right gravity conversion and centering
-  private final FloatPropertyCompat<WindowManager.LayoutParams> xProperty =
-      new FloatPropertyCompat<LayoutParams>("xProperty") {
-        @Override
-        public float getValue(LayoutParams windowParams) {
-          int realX = windowParams.x;
-          // Get bubble center position from real position
-          if (bubble.getDrawerVisibility() == View.INVISIBLE) {
-            realX += bubbleExpandedViewWidth / 2 + bubbleShadowPaddingHorizontal * 2;
-          } else {
-            realX += bubbleSize / 2 + bubbleShadowPaddingHorizontal;
-          }
-          if (relativeToRight(windowParams)) {
-            // If gravity is right, get distant from bubble center position to screen right edge
-            int displayWidth = context.getResources().getDisplayMetrics().widthPixels;
-            realX = displayWidth - realX;
-          }
-          return clamp(realX, minX, maxX);
-        }
-
-        @Override
-        public void setValue(LayoutParams windowParams, float value) {
-          boolean wasOnRight = (windowParams.gravity & Gravity.RIGHT) == Gravity.RIGHT;
-          int displayWidth = context.getResources().getDisplayMetrics().widthPixels;
-          boolean onRight;
-          Integer gravityOverride = bubble.getGravityOverride();
-          if (gravityOverride == null) {
-            onRight = value > displayWidth / 2;
-          } else {
-            onRight = (gravityOverride & Gravity.RIGHT) == Gravity.RIGHT;
-          }
-          // Get real position from bubble center position
-          int centeringOffset;
-          if (bubble.getDrawerVisibility() == View.INVISIBLE) {
-            centeringOffset = bubbleExpandedViewWidth / 2 + bubbleShadowPaddingHorizontal * 2;
-          } else {
-            centeringOffset = bubbleSize / 2 + bubbleShadowPaddingHorizontal;
-          }
-          windowParams.x =
-              (int) (onRight ? (displayWidth - value - centeringOffset) : value - centeringOffset);
-          windowParams.gravity = Gravity.TOP | (onRight ? Gravity.RIGHT : Gravity.LEFT);
-          if (bubble.isVisible()) {
-            windowManager.updateViewLayout(bubble.getRootView(), windowParams);
-            if (onRight != wasOnRight) {
-              bubble.onLeftRightSwitch(onRight);
-            }
-          }
-        }
-      };
-
-  private final FloatPropertyCompat<WindowManager.LayoutParams> yProperty =
-      new FloatPropertyCompat<LayoutParams>("yProperty") {
-        @Override
-        public float getValue(LayoutParams object) {
-          return clamp(object.y + bubbleSize, minY, maxY);
-        }
-
-        @Override
-        public void setValue(LayoutParams object, float value) {
-          object.y = (int) value - bubbleSize;
-          if (bubble.isVisible()) {
-            windowManager.updateViewLayout(bubble.getRootView(), object);
-          }
-        }
-      };
-
-  public NewMoveHandler(@NonNull View targetView, @NonNull NewBubble bubble) {
-    this.bubble = bubble;
-    context = targetView.getContext();
-    windowManager = context.getSystemService(WindowManager.class);
-
-    bubbleSize = context.getResources().getDimensionPixelSize(R.dimen.bubble_size);
-    bubbleShadowPaddingHorizontal =
-        context.getResources().getDimensionPixelSize(R.dimen.bubble_shadow_padding_size_horizontal);
-    bubbleExpandedViewWidth =
-        context.getResources().getDimensionPixelSize(R.dimen.bubble_expanded_width);
-    // The following value is based on bubble center
-    minX =
-        context.getResources().getDimensionPixelOffset(R.dimen.bubble_off_screen_size_horizontal)
-            + bubbleSize / 2;
-    minY =
-        context.getResources().getDimensionPixelOffset(R.dimen.bubble_safe_margin_vertical)
-            + bubbleSize / 2;
-    maxX = context.getResources().getDisplayMetrics().widthPixels - minX;
-    maxY = context.getResources().getDisplayMetrics().heightPixels - minY;
-
-    // Squared because it will be compared against the square of the touch delta. This is more
-    // efficient than needing to take a square root.
-    touchSlopSquared = (float) Math.pow(ViewConfiguration.get(context).getScaledTouchSlop(), 2);
-
-    bottomActionViewController = new BottomActionViewController(context);
-
-    targetView.setOnTouchListener(this);
-  }
-
-  public void setClickable(boolean clickable) {
-    this.clickable = clickable;
-  }
-
-  public boolean isMoving() {
-    return isMoving;
-  }
-
-  public void undoGravityOverride() {
-    LayoutParams windowParams = bubble.getWindowParams();
-    xProperty.setValue(windowParams, xProperty.getValue(windowParams));
-  }
-
-  public void snapToBounds() {
-    ensureSprings();
-
-    moveXAnimation.animateToFinalPosition(relativeToRight(bubble.getWindowParams()) ? maxX : minX);
-    moveYAnimation.animateToFinalPosition(yProperty.getValue(bubble.getWindowParams()));
-  }
-
-  public int getMoveUpDistance(int deltaAllowed) {
-    int currentY = (int) yProperty.getValue(bubble.getWindowParams());
-    int currentDelta = maxY - currentY;
-    return currentDelta >= deltaAllowed ? 0 : deltaAllowed - currentDelta;
-  }
-
-  @Override
-  public boolean onTouch(View v, MotionEvent event) {
-    float eventX = event.getRawX();
-    float eventY = event.getRawY();
-    switch (event.getActionMasked()) {
-      case MotionEvent.ACTION_DOWN:
-        firstX = eventX;
-        firstY = eventY;
-        velocityTracker = VelocityTracker.obtain();
-        break;
-      case MotionEvent.ACTION_MOVE:
-        if (isMoving || hasExceededTouchSlop(event)) {
-          if (!isMoving) {
-            isMoving = true;
-            bubble.onMoveStart();
-            bottomActionViewController.createAndShowBottomActionView();
-          }
-          bottomActionViewController.highlightIfHover(eventX, eventY);
-
-          ensureSprings();
-
-          moveXAnimation.animateToFinalPosition(clamp(eventX, minX, maxX));
-          moveYAnimation.animateToFinalPosition(clamp(eventY, minY, maxY));
-        }
-
-        velocityTracker.addMovement(event);
-        break;
-      case MotionEvent.ACTION_UP:
-        if (isMoving) {
-          ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
-          velocityTracker.computeCurrentVelocity(
-              1000, viewConfiguration.getScaledMaximumFlingVelocity());
-          float xVelocity = velocityTracker.getXVelocity();
-          float yVelocity = velocityTracker.getYVelocity();
-          boolean isFling = isFling(xVelocity, yVelocity);
-
-          if (isFling) {
-            Point target =
-                findTarget(
-                    xVelocity,
-                    yVelocity,
-                    (int) xProperty.getValue(bubble.getWindowParams()),
-                    (int) yProperty.getValue(bubble.getWindowParams()));
-
-            moveXAnimation.animateToFinalPosition(target.x);
-            moveYAnimation.animateToFinalPosition(target.y);
-          } else if (bottomActionViewController.isDismissHighlighted()) {
-            bubble.bottomActionDismiss();
-          } else if (bottomActionViewController.isEndCallHighlighted()) {
-            bubble.bottomActionEndCall();
-          } else {
-            snapX();
-          }
-          isMoving = false;
-          bubble.onMoveFinish();
-          bottomActionViewController.destroyBottomActionView();
-        } else {
-          v.performClick();
-          if (clickable) {
-            bubble.primaryButtonClick();
-          }
-        }
-        break;
-      case MotionEvent.ACTION_CANCEL:
-        if (isMoving) {
-          snapX();
-          isMoving = false;
-          bubble.onMoveFinish();
-          bottomActionViewController.destroyBottomActionView();
-        }
-        break;
-      default: // fall out
-    }
-    return true;
-  }
-
-  private void ensureSprings() {
-    if (moveXAnimation == null) {
-      moveXAnimation = new SpringAnimation(bubble.getWindowParams(), xProperty);
-      moveXAnimation.setSpring(new SpringForce());
-      moveXAnimation.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY);
-      // Moving when expanded makes expanded view INVISIBLE, and the whole view is not at the
-      // boundary. It's time to create a viewHolder.
-      moveXAnimation.addEndListener(
-          (animation, canceled, value, velocity) -> {
-            if (!isMoving && bubble.getDrawerVisibility() == View.INVISIBLE) {
-              bubble.replaceViewHolder();
-            }
-          });
-    }
-
-    if (moveYAnimation == null) {
-      moveYAnimation = new SpringAnimation(bubble.getWindowParams(), yProperty);
-      moveYAnimation.setSpring(new SpringForce());
-      moveYAnimation.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY);
-    }
-  }
-
-  private Point findTarget(float xVelocity, float yVelocity, int startX, int startY) {
-    if (scroller == null) {
-      scroller = new Scroller(context);
-      scroller.setFriction(ViewConfiguration.getScrollFriction() * SCROLL_FRICTION_MULTIPLIER);
-    }
-
-    // Find where a fling would end vertically
-    scroller.fling(startX, startY, (int) xVelocity, (int) yVelocity, minX, maxX, minY, maxY);
-    int targetY = scroller.getFinalY();
-    scroller.abortAnimation();
-
-    // If the x component of the velocity is above the minimum fling velocity, use velocity to
-    // determine edge. Otherwise use its starting position
-    boolean pullRight = isFling(xVelocity, 0) ? xVelocity > 0 : isOnRightHalf(startX);
-    return new Point(pullRight ? maxX : minX, targetY);
-  }
-
-  private boolean isFling(float xVelocity, float yVelocity) {
-    int minFlingVelocity =
-        ViewConfiguration.get(context).getScaledMinimumFlingVelocity() * MIN_FLING_VELOCITY_FACTOR;
-    return getMagnitudeSquared(xVelocity, yVelocity) > minFlingVelocity * minFlingVelocity;
-  }
-
-  private boolean isOnRightHalf(float currentX) {
-    return currentX > (minX + maxX) / 2;
-  }
-
-  private void snapX() {
-    // Check if x value is closer to min or max
-    boolean pullRight = isOnRightHalf(xProperty.getValue(bubble.getWindowParams()));
-    moveXAnimation.animateToFinalPosition(pullRight ? maxX : minX);
-  }
-
-  private boolean relativeToRight(LayoutParams windowParams) {
-    return (windowParams.gravity & Gravity.RIGHT) == Gravity.RIGHT;
-  }
-
-  private boolean hasExceededTouchSlop(MotionEvent event) {
-    return getMagnitudeSquared(event.getRawX() - firstX, event.getRawY() - firstY)
-        > touchSlopSquared;
-  }
-
-  private float getMagnitudeSquared(float deltaX, float deltaY) {
-    return deltaX * deltaX + deltaY * deltaY;
-  }
-}
diff --git a/java/com/android/newbubble/NewWindowRoot.java b/java/com/android/newbubble/NewWindowRoot.java
deleted file mode 100644
index da24b71..0000000
--- a/java/com/android/newbubble/NewWindowRoot.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 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.newbubble;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.support.annotation.NonNull;
-import android.view.KeyEvent;
-import android.widget.FrameLayout;
-
-/**
- * ViewGroup that handles some overlay window concerns. Allows back button and configuration change
- * events to be listened for via interfaces.
- */
-public class NewWindowRoot extends FrameLayout {
-
-  /** Callback for when the back button is pressed while this window is in focus */
-  public interface OnBackPressedListener {
-    boolean onBackPressed();
-  }
-
-  /** Callback for when the Configuration changes for this window */
-  public interface OnConfigurationChangedListener {
-    void onConfigurationChanged(Configuration newConfiguration);
-  }
-
-  private OnBackPressedListener backPressedListener;
-  private OnConfigurationChangedListener configurationChangedListener;
-
-  public NewWindowRoot(@NonNull Context context) {
-    super(context);
-  }
-
-  public void setOnBackPressedListener(OnBackPressedListener listener) {
-    backPressedListener = listener;
-  }
-
-  public void setOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
-    configurationChangedListener = listener;
-  }
-
-  @Override
-  public boolean dispatchKeyEvent(KeyEvent event) {
-    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && backPressedListener != null) {
-      if (event.getAction() == KeyEvent.ACTION_UP) {
-        return backPressedListener.onBackPressed();
-      }
-      return true;
-    }
-    return super.dispatchKeyEvent(event);
-  }
-
-  @Override
-  public void dispatchConfigurationChanged(Configuration newConfig) {
-    super.dispatchConfigurationChanged(newConfig);
-    if (configurationChangedListener != null) {
-      configurationChangedListener.onConfigurationChanged(newConfig);
-    }
-  }
-}
diff --git a/java/com/android/newbubble/RoundedRectRevealOutlineProvider.java b/java/com/android/newbubble/RoundedRectRevealOutlineProvider.java
deleted file mode 100644
index 2ade6cf..0000000
--- a/java/com/android/newbubble/RoundedRectRevealOutlineProvider.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2017 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.newbubble;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.graphics.Outline;
-import android.graphics.Rect;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-
-/**
- * A {@link ViewOutlineProvider} that provides an outline that interpolates between two radii and
- * two {@link Rect}s.
- *
- * <p>An example usage of this provider is an outline that starts out as a circle and ends as a
- * rounded rectangle.
- */
-public class RoundedRectRevealOutlineProvider extends ViewOutlineProvider {
-  private final float startRadius;
-  private final float endRadius;
-
-  private final Rect startRect;
-  private final Rect endRect;
-
-  private final Rect outline;
-  private float outlineRadius;
-
-  public RoundedRectRevealOutlineProvider(
-      float startRadius, float endRadius, Rect startRect, Rect endRect) {
-    this.startRadius = startRadius;
-    this.endRadius = endRadius;
-    this.startRect = startRect;
-    this.endRect = endRect;
-
-    outline = new Rect();
-  }
-
-  @Override
-  public void getOutline(View v, Outline outline) {
-    outline.setRoundRect(this.outline, outlineRadius);
-  }
-
-  /** Sets the progress, from 0 to 1, of the reveal animation. */
-  public void setProgress(float progress) {
-    outlineRadius = (1 - progress) * startRadius + progress * endRadius;
-
-    outline.left = (int) ((1 - progress) * startRect.left + progress * endRect.left);
-    outline.top = (int) ((1 - progress) * startRect.top + progress * endRect.top);
-    outline.right = (int) ((1 - progress) * startRect.right + progress * endRect.right);
-    outline.bottom = (int) ((1 - progress) * startRect.bottom + progress * endRect.bottom);
-  }
-
-  ValueAnimator createRevealAnimator(final View revealView, boolean isReversed) {
-    ValueAnimator valueAnimator =
-        isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
-
-    valueAnimator.addListener(
-        new AnimatorListenerAdapter() {
-          private boolean wasCanceled = false;
-
-          @Override
-          public void onAnimationStart(Animator animation) {
-            revealView.setOutlineProvider(RoundedRectRevealOutlineProvider.this);
-            revealView.setClipToOutline(true);
-          }
-
-          @Override
-          public void onAnimationCancel(Animator animation) {
-            wasCanceled = true;
-          }
-
-          @Override
-          public void onAnimationEnd(Animator animation) {
-            if (!wasCanceled) {
-              revealView.setOutlineProvider(ViewOutlineProvider.BACKGROUND);
-              revealView.setClipToOutline(false);
-            }
-          }
-        });
-
-    valueAnimator.addUpdateListener(
-        (currentValueAnimator) -> {
-          float progress = (Float) currentValueAnimator.getAnimatedValue();
-          setProgress(progress);
-          revealView.invalidateOutline();
-        });
-    return valueAnimator;
-  }
-}
diff --git a/java/com/android/newbubble/res/drawable/bottom_action_scrim.xml b/java/com/android/newbubble/res/drawable/bottom_action_scrim.xml
deleted file mode 100644
index 1109aa6..0000000
--- a/java/com/android/newbubble/res/drawable/bottom_action_scrim.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 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
-  -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-  <gradient
-      android:angle="90"
-      android:endColor="@android:color/transparent"
-      android:startColor="#FF000000"/>
-</shape>
diff --git a/java/com/android/newbubble/res/drawable/bubble_background_with_radius.xml b/java/com/android/newbubble/res/drawable/bubble_background_with_radius.xml
deleted file mode 100644
index 4fd871c..0000000
--- a/java/com/android/newbubble/res/drawable/bubble_background_with_radius.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 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
-  -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-  <corners
-      android:bottomRightRadius="@dimen/bubble_radius"
-      android:topRightRadius="@dimen/bubble_radius"
-      android:bottomLeftRadius="@dimen/bubble_radius"
-      android:topLeftRadius="@dimen/bubble_radius"/>
-  <solid android:color="@android:color/white"/>
-</shape>
diff --git a/java/com/android/newbubble/res/drawable/bubble_pill_down.xml b/java/com/android/newbubble/res/drawable/bubble_pill_down.xml
deleted file mode 100644
index 721e6fc..0000000
--- a/java/com/android/newbubble/res/drawable/bubble_pill_down.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 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
-  -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-  <corners
-      android:bottomRightRadius="@dimen/bubble_radius"
-      android:bottomLeftRadius="@dimen/bubble_radius"/>
-  <solid android:color="@android:color/white"/>
-</shape>
diff --git a/java/com/android/newbubble/res/drawable/bubble_pill_up.xml b/java/com/android/newbubble/res/drawable/bubble_pill_up.xml
deleted file mode 100644
index 9dc0395..0000000
--- a/java/com/android/newbubble/res/drawable/bubble_pill_up.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 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
-  -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-  <corners
-      android:topRightRadius="8dp"
-      android:topLeftRadius="8dp"/>
-  <solid android:color="@android:color/white"/>
-</shape>
diff --git a/java/com/android/newbubble/res/drawable/bubble_ripple_pill_up.xml b/java/com/android/newbubble/res/drawable/bubble_ripple_pill_up.xml
deleted file mode 100644
index 77147f8..0000000
--- a/java/com/android/newbubble/res/drawable/bubble_ripple_pill_up.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 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
-  -->
-
-<ripple xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="@color/bubble_ripple_color">
-  <item android:drawable="@drawable/bubble_pill_up"/>
-</ripple>
diff --git a/java/com/android/newbubble/res/drawable/bubble_shape_circle.xml b/java/com/android/newbubble/res/drawable/bubble_shape_circle.xml
deleted file mode 100644
index b188e9d..0000000
--- a/java/com/android/newbubble/res/drawable/bubble_shape_circle.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 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
-  -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-  <size
-      android:width="@dimen/bubble_size"
-      android:height="@dimen/bubble_size"/>
-  <solid android:color="@android:color/transparent"/>
-</shape>
diff --git a/java/com/android/newbubble/res/drawable/bubble_shape_circle_small.xml b/java/com/android/newbubble/res/drawable/bubble_shape_circle_small.xml
deleted file mode 100644
index 73b9cf3..0000000
--- a/java/com/android/newbubble/res/drawable/bubble_shape_circle_small.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 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
-  -->
-
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
-  <solid android:color="@android:color/white"/>
-  <size android:width="@dimen/bubble_small_icon_size"
-      android:height="@dimen/bubble_small_icon_size"/>
-</shape>
diff --git a/java/com/android/newbubble/res/layout/bottom_action_base.xml b/java/com/android/newbubble/res/layout/bottom_action_base.xml
deleted file mode 100644
index b4d7c89..0000000
--- a/java/com/android/newbubble/res/layout/bottom_action_base.xml
+++ /dev/null
@@ -1,88 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/bubble_bottom_action_view_height"
-    android:weightSum="2"
-    android:orientation="horizontal"
-    android:gravity="center"
-    android:background="@drawable/bottom_action_scrim"
-    android:contentDescription="@string/a11y_bubble_bottom_action_description">
-
-  <!-- Add space to make sure text is not off screen when scaled. (1 - 1/1.3) / 2 ~= 0.11 -->
-  <View
-      android:layout_width="0dp"
-      android:layout_height="match_parent"
-      android:layout_weight="0.11"/>
-
-  <LinearLayout
-      android:id="@+id/bottom_action_dismiss_layout"
-      android:layout_width="0dp"
-      android:layout_height="match_parent"
-      android:layout_weight="0.78"
-      android:gravity="center"
-      android:contentDescription="@string/bubble_bottom_action_hide">
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/bubble_bottom_action_text_offset"
-        android:maxLines="2"
-        android:ellipsize="end"
-        android:textColor="@color/bubble_button_color_white"
-        android:textSize="16sp"
-        android:fontFamily="roboto-medium"
-        android:text="@string/bubble_bottom_action_hide"
-        android:drawableStart="@drawable/quantum_ic_clear_vd_theme_24"
-        android:drawableTint="@color/bubble_button_color_white"
-        android:drawablePadding="10dp"
-        android:elevation="2dp"/>
-  </LinearLayout>
-
-  <View
-      android:layout_width="0dp"
-      android:layout_height="match_parent"
-      android:layout_weight="0.22"/>
-
-  <LinearLayout
-      android:id="@+id/bottom_action_end_call_layout"
-      android:layout_width="0dp"
-      android:layout_height="match_parent"
-      android:layout_weight="0.78"
-      android:gravity="center"
-      android:contentDescription="@string/bubble_bottom_action_end_call">
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/bubble_bottom_action_text_offset"
-        android:maxLines="2"
-        android:ellipsize="end"
-        android:textColor="@color/bubble_button_color_white"
-        android:textSize="16sp"
-        android:fontFamily="roboto-medium"
-        android:text="@string/bubble_bottom_action_end_call"
-        android:drawableStart="@drawable/quantum_ic_call_end_vd_theme_24"
-        android:drawableTint="@color/bubble_button_color_white"
-        android:drawablePadding="10dp"
-        android:elevation="2dp"/>
-  </LinearLayout>
-
-  <View
-      android:layout_width="0dp"
-      android:layout_height="match_parent"
-      android:layout_weight="0.11"/>
-
-</LinearLayout>
\ No newline at end of file
diff --git a/java/com/android/newbubble/res/layout/new_bubble_base.xml b/java/com/android/newbubble/res/layout/new_bubble_base.xml
deleted file mode 100644
index 9b8250d..0000000
--- a/java/com/android/newbubble/res/layout/new_bubble_base.xml
+++ /dev/null
@@ -1,139 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:clipChildren="true"
-    android:clipToPadding="false"
-    android:layoutDirection="ltr"
-    tools:theme="@style/Theme.AppCompat">
-  <RelativeLayout
-      android:id="@+id/bubble_primary_container"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:layout_centerHorizontal="true"
-      android:animateLayoutChanges="true"
-      android:clipChildren="false"
-      android:clipToPadding="false">
-    <FrameLayout
-        android:id="@+id/bubble_button_primary"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/bubble_shadow_padding_size_horizontal"
-        android:layout_marginEnd="@dimen/bubble_shadow_padding_size_horizontal"
-        android:layout_marginTop="@dimen/bubble_shadow_padding_size_vertical"
-        android:layout_marginBottom="@dimen/bubble_shadow_padding_size_vertical"
-        android:contentDescription="@string/a11y_bubble_description"
-        android:measureAllChildren="false">
-      <ImageView
-          android:id="@+id/bubble_icon_avatar"
-          android:layout_width="@dimen/bubble_size"
-          android:layout_height="@dimen/bubble_size"
-          android:background="@android:drawable/ic_btn_speak_now"
-          android:elevation="@dimen/bubble_elevation"/>
-      <ImageView
-          android:id="@+id/bubble_icon_primary"
-          android:layout_width="@dimen/bubble_small_icon_size"
-          android:layout_height="@dimen/bubble_small_icon_size"
-          android:layout_gravity="bottom|right"
-          android:padding="@dimen/bubble_small_icon_padding"
-          android:tint="@android:color/white"
-          android:tintMode="src_in"
-          android:background="@drawable/bubble_shape_circle_small"
-          android:measureAllChildren="false"
-          tools:backgroundTint="#FF0000AA"
-          tools:src="@android:drawable/ic_btn_speak_now"
-          android:elevation="@dimen/bubble_dragging_elevation"/>
-    </FrameLayout>
-  </RelativeLayout>
-  <!-- The RelativeLayout below serves as boundary for @id/bubble_expanded_layout during animation -->
-  <RelativeLayout
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:layout_marginTop="@dimen/bubble_shadow_padding_size_vertical_minus"
-      android:clipChildren="true"
-      android:clipToPadding="false"
-      android:layout_below="@id/bubble_primary_container">
-    <RelativeLayout
-        android:id="@+id/bubble_expanded_layout"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:paddingStart="@dimen/bubble_shadow_padding_size_horizontal_double"
-        android:paddingEnd="@dimen/bubble_shadow_padding_size_horizontal_double"
-        android:paddingBottom="@dimen/bubble_shadow_padding_size_vertical"
-        android:clipChildren="false"
-        android:clipToPadding="false"
-        android:visibility="gone"
-        tools:visibility="visible">
-      <RelativeLayout
-          android:id="@+id/bubble_triangle"
-          android:layout_width="12dp"
-          android:layout_height="12dp"
-          android:layout_marginTop="7dp"
-          android:layout_marginBottom="-6dp"
-          android:layout_centerHorizontal="true"
-          android:background="@color/background_dialer_white"
-          android:elevation="@dimen/bubble_expanded_elevation"
-          android:rotation="45">
-      </RelativeLayout>
-      <RelativeLayout
-          android:id="@+id/bubble_expanded_menu"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:layout_below="@id/bubble_triangle"
-          android:background="@drawable/bubble_background_with_radius"
-          android:elevation="@dimen/bubble_expanded_elevation"
-          android:clipChildren="false"
-          android:clipToPadding="false"
-          android:layoutDirection="inherit">
-        <com.android.newbubble.NewCheckableButton
-            android:id="@+id/bubble_button_full_screen"
-            android:layout_marginTop="8dp"
-            android:textColor="@color/bubble_button_color_grey"
-            android:background="@drawable/bubble_ripple_pill_up"
-            android:drawableTint="@color/bubble_button_color_grey"
-            style="@style/CheckableButton"/>
-        <com.android.newbubble.NewCheckableButton
-            android:id="@+id/bubble_button_mute"
-            android:layout_below="@id/bubble_button_full_screen"
-            android:textColor="@color/bubble_button_color_grey"
-            android:background="@color/background_dialer_white"
-            android:drawableTint="@color/bubble_button_color_grey"
-            style="@style/CheckableButtonWithSelectableItemBackground"/>
-        <com.android.newbubble.NewCheckableButton
-            android:id="@+id/bubble_button_audio_route"
-            android:layout_below="@id/bubble_button_mute"
-            android:textColor="@color/bubble_button_color_grey"
-            android:background="@color/background_dialer_white"
-            android:drawableTint="@color/bubble_button_color_grey"
-            style="@style/CheckableButtonWithSelectableItemBackground"/>
-        <com.android.newbubble.NewCheckableButton
-            android:id="@+id/bubble_button_end_call"
-            android:layout_below="@id/bubble_button_audio_route"
-            android:layout_marginTop="@dimen/bubble_expanded_separator_height"
-            android:textColor="@color/bubble_button_color_white"
-            android:background="@drawable/bubble_pill_down"
-            android:backgroundTint="@color/dialer_end_call_button_color"
-            android:foreground="?attr/selectableItemBackground"
-            android:drawableTint="@color/bubble_button_color_white"
-            style="@style/CheckableButton"/>
-      </RelativeLayout>
-    </RelativeLayout>
-  </RelativeLayout>
-</RelativeLayout>
diff --git a/java/com/android/newbubble/res/values/colors.xml b/java/com/android/newbubble/res/values/colors.xml
deleted file mode 100644
index 74ad85c..0000000
--- a/java/com/android/newbubble/res/values/colors.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 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
-  -->
-
-<resources>
-  <color name="bubble_primary_background_darken">#33000000</color>
-
-  <color name="bubble_ripple_color">@color/bubble_primary_background_darken</color>
-  <color name="bubble_button_color_grey">@color/icon_color_grey</color>
-  <color name="bubble_button_color_white">@color/dialer_primary_text_color_white</color>
-  <color name="bubble_button_color_blue">@color/dialer_theme_color</color>
-</resources>
diff --git a/java/com/android/newbubble/res/values/strings.xml b/java/com/android/newbubble/res/values/strings.xml
deleted file mode 100644
index 7ef61ce..0000000
--- a/java/com/android/newbubble/res/values/strings.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 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
-  -->
-
-<resources>
-  <!-- A string for Talkback to read when accessibility user touch bubble. -->
-  <string name="a11y_bubble_description">Dialer bubble</string>
-  <!-- A string to describe bubble bottom actions for accessibility user. -->
-  <string name="a11y_bubble_bottom_action_description">Bottom action buttons</string>
-  <!-- A string to describe available action for accessibility user. It will be read as "Actions:
-    double tap to expand call action menu". -->
-  <string name="a11y_bubble_primary_button_expand_action">Expand call action menu</string>
-  <!-- A string to describe available action for accessibility user. It will be read as "Actions:
-    double tap to collapse call action menu". -->
-  <string name="a11y_bubble_primary_button_collapse_action">Collapse call action menu</string>
-
-  <!-- The label of drag-and-drop target for dismissing bubble. [CHAR LIMIT=20]-->
-  <string name="bubble_bottom_action_hide">Hide</string>
-  <!-- The label of drag-and-drop target for ending call. [CHAR LIMIT=20]-->
-  <string name="bubble_bottom_action_end_call">End call</string>
-</resources>
diff --git a/java/com/android/newbubble/res/values/styles.xml b/java/com/android/newbubble/res/values/styles.xml
deleted file mode 100644
index 5a900be..0000000
--- a/java/com/android/newbubble/res/values/styles.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 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
-  -->
-
-<resources>
-  <style name="CheckableButton">
-    <item name="android:layout_width">@dimen/bubble_expanded_width</item>
-    <item name="android:layout_height">44dp</item>
-
-    <item name="android:gravity">center_vertical</item>
-    <item name="android:maxLines">2</item>
-    <item name="android:ellipsize">end</item>
-    <item name="android:paddingStart">@dimen/bubble_button_padding_horizontal</item>
-    <item name="android:paddingEnd">@dimen/bubble_button_padding_horizontal</item>
-    <item name="android:drawablePadding">@dimen/bubble_icon_padding</item>
-  </style>
-
-  <style name="SelectableItemTheme">
-    <item name="colorControlHighlight">@color/bubble_ripple_color</item>
-  </style>
-  <style name="CheckableButtonWithSelectableItemBackground" parent="CheckableButton">
-    <item name="android:theme">@style/SelectableItemTheme</item>
-    <item name="android:foreground">?attr/selectableItemBackground</item>
-
-  </style>
-</resources>
\ No newline at end of file
diff --git a/java/com/android/newbubble/res/values/values.xml b/java/com/android/newbubble/res/values/values.xml
deleted file mode 100644
index f449c9b..0000000
--- a/java/com/android/newbubble/res/values/values.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2017 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
-  -->
-
-<resources>
-  <dimen name="bubble_size">56dp</dimen>
-  <dimen name="bubble_icon_padding">16dp</dimen>
-  <dimen name="bubble_dragging_elevation_change">6dp</dimen>
-  <dimen name="bubble_dragging_elevation">12dp</dimen>
-
-  <dimen name="bubble_button_height">36dp</dimen>
-  <dimen name="bubble_button_icon_padding">16dp</dimen>
-  <dimen name="bubble_button_padding_vertical">12dp</dimen>
-  <dimen name="bubble_button_padding_horizontal">16dp</dimen>
-
-  <dimen name="bubble_off_screen_size_horizontal">-4dp</dimen>
-  <!-- 36dp - 16dp(bubble_shadow_padding_size_vertical) -->
-  <dimen name="bubble_safe_margin_vertical">20dp</dimen>
-
-  <dimen name="bubble_shadow_padding_size_vertical">16dp</dimen>
-  <dimen name="bubble_shadow_padding_size_vertical_minus">-16dp</dimen>
-  <dimen name="bubble_shadow_padding_size_horizontal">12dp</dimen>
-  <dimen name="bubble_shadow_padding_size_horizontal_double">24dp</dimen>
-
-  <dimen name="bubble_elevation">6dp</dimen>
-  <dimen name="bubble_expanded_elevation">8dp</dimen>
-  <dimen name="bubble_expanded_width">176dp</dimen>
-  <dimen name="bubble_radius">16dp</dimen>
-  <dimen name="bubble_expanded_separator_height">8dp</dimen>
-  <dimen name="bubble_small_icon_size">24dp</dimen>
-  <dimen name="bubble_small_icon_padding">4dp</dimen>
-
-  <dimen name="bubble_bottom_action_view_height">124dp</dimen>
-  <dimen name="bubble_bottom_action_text_offset">28dp</dimen>
-</resources>
diff --git a/java/com/android/newbubble/stub/NewBubbleStub.java b/java/com/android/newbubble/stub/NewBubbleStub.java
new file mode 100644
index 0000000..4079aaa
--- /dev/null
+++ b/java/com/android/newbubble/stub/NewBubbleStub.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 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.newbubble.stub;
+
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import com.android.newbubble.NewBubble;
+import com.android.newbubble.NewBubbleInfo;
+import com.android.newbubble.NewBubbleInfo.Action;
+import java.util.List;
+import javax.inject.Inject;
+
+public class NewBubbleStub implements NewBubble {
+
+  @Inject
+  public NewBubbleStub() {}
+
+  @Override
+  public void show() {}
+
+  @Override
+  public void hide() {}
+
+  @Override
+  public void hideAndReset() {}
+
+  @Override
+  public boolean isVisible() {
+    return false;
+  }
+
+  @Override
+  public void setBubbleInfo(@NonNull NewBubbleInfo bubbleInfo) {}
+
+  @Override
+  public void updateActions(@NonNull List<Action> actions) {}
+
+  @Override
+  public void updatePhotoAvatar(@NonNull Drawable avatar) {}
+
+  @Override
+  public void updateAvatar(@NonNull Drawable avatar) {}
+
+  @Override
+  public void showText(@NonNull CharSequence text) {}
+}
diff --git a/java/com/android/newbubble/stub/StubNewBubbleModule.java b/java/com/android/newbubble/stub/StubNewBubbleModule.java
new file mode 100644
index 0000000..227ba5e
--- /dev/null
+++ b/java/com/android/newbubble/stub/StubNewBubbleModule.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 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.newbubble.stub;
+
+import com.android.newbubble.NewBubble;
+import dagger.Binds;
+import dagger.Module;
+import javax.inject.Singleton;
+
+@Module
+public abstract class StubNewBubbleModule {
+
+  @Binds
+  @Singleton
+  public abstract NewBubble bindsNewBubble(NewBubbleStub newBubbleStub);
+}
diff --git a/packages.mk b/packages.mk
index 4985425..40e4d02 100644
--- a/packages.mk
+++ b/packages.mk
@@ -85,7 +85,6 @@
 	com.android.incallui.telecomeventui \
 	com.android.incallui.video.impl \
 	com.android.incallui.video.protocol \
-	com.android.newbubble \
 	com.android.phoneapphelper \
 	com.android.voicemail \
 	com.android.voicemail.impl \