Fix bubble for conference call.

Fix bubble conference call bugs once and for all.
ReturnToCallController.onDsconnect() is called twice when ending the last call
in conference call: one for the last call itself, one for the parent call. And
we should do nothing for the parent call. This change avoids extra handling in
Bubble.showText() and Bubble.hideAndReset().

Test: ReturnToCallControllerTest
PiperOrigin-RevId: 165195412
Change-Id: Ib21fac6dd7ad9fe85c3070ce1295f63a91c61a02
diff --git a/java/com/android/incallui/ReturnToCallController.java b/java/com/android/incallui/ReturnToCallController.java
index 8e4b9cc..b0bafac 100644
--- a/java/com/android/incallui/ReturnToCallController.java
+++ b/java/com/android/incallui/ReturnToCallController.java
@@ -142,13 +142,22 @@
 
   @Override
   public void onDisconnect(DialerCall call) {
-    boolean hasAnotherCall = CallList.getInstance().getActiveOrBackgroundCall() != null;
+    if (call.wasParentCall()) {
+      // It's disconnected after the last child call is disconnected, and we already did everything
+      // for the last child.
+      LogUtil.i(
+          "ReturnToCallController.onDisconnect", "being called for a parent call and do nothing");
+      return;
+    }
     if (bubble != null
         && bubble.isVisible()
-        && (!TelecomUtil.isInCall(context) || hasAnotherCall)) {
+        && (!TelecomUtil.isInCall(context)
+            || CallList.getInstance().getActiveOrBackgroundCall() != null)) {
       bubble.showText(context.getText(R.string.incall_call_ended));
     }
-    if (!hasAnotherCall) {
+    // For conference call, we should hideAndReset for the last disconnected child call while the
+    // parent call is still there.
+    if (!CallList.getInstance().hasNonParentActiveOrBackgroundCall()) {
       hideAndReset();
     }
   }
diff --git a/java/com/android/incallui/call/CallList.java b/java/com/android/incallui/call/CallList.java
index d932c24..d0931dd 100644
--- a/java/com/android/incallui/call/CallList.java
+++ b/java/com/android/incallui/call/CallList.java
@@ -536,6 +536,22 @@
   }
 
   /**
+   * Return if there is any active or background call which was not a parent call (never had a child
+   * call)
+   */
+  public boolean hasNonParentActiveOrBackgroundCall() {
+    for (DialerCall call : mCallById.values()) {
+      if ((call.getState() == State.ACTIVE
+              || call.getState() == State.ONHOLD
+              || call.getState() == State.CONFERENCED)
+          && !call.wasParentCall()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
    * This is called when the service disconnects, either expectedly or unexpectedly. For the
    * expected case, it's because we have no calls left. For the unexpected case, it is likely a
    * crash of phone and we need to clean up our calls manually. Without phone, there can be no
diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java
index a954ee9..378f920 100644
--- a/java/com/android/incallui/call/DialerCall.java
+++ b/java/com/android/incallui/call/DialerCall.java
@@ -429,6 +429,10 @@
     }
   }
 
+  public boolean wasParentCall() {
+    return mLogState.conferencedCalls != 0;
+  }
+
   private void update() {
     Trace.beginSection("DialerCall.update");
     int oldState = getState();