Merge "Implement InCallUiLock"
diff --git a/java/com/android/incallui/AnswerScreenPresenter.java b/java/com/android/incallui/AnswerScreenPresenter.java
index 58231d5..b9a84ae 100644
--- a/java/com/android/incallui/AnswerScreenPresenter.java
+++ b/java/com/android/incallui/AnswerScreenPresenter.java
@@ -34,6 +34,7 @@
 import com.android.incallui.call.CallList;
 import com.android.incallui.call.DialerCall;
 import com.android.incallui.call.DialerCallListener;
+import com.android.incallui.incalluilock.InCallUiLock;
 
 /** Manages changes for an incoming call screen. */
 public class AnswerScreenPresenter
@@ -72,19 +73,18 @@
   }
 
   @Override
+  public InCallUiLock acquireInCallUiLock(String tag) {
+    return InCallPresenter.getInstance().acquireInCallUiLock(tag);
+  }
+
+  @Override
   public void onAnswerScreenUnready() {
     call.removeCannedTextResponsesLoadedListener(this);
   }
 
   @Override
-  public void onDismissDialog() {
-    InCallPresenter.getInstance().onDismissDialog();
-  }
-
-  @Override
   public void onRejectCallWithMessage(String message) {
     call.reject(true /* rejectWithMessage */, message);
-    onDismissDialog();
     addTimeoutCheck();
   }
 
diff --git a/java/com/android/incallui/AnswerScreenPresenterStub.java b/java/com/android/incallui/AnswerScreenPresenterStub.java
index 2f9e608..99f1f2c 100644
--- a/java/com/android/incallui/AnswerScreenPresenterStub.java
+++ b/java/com/android/incallui/AnswerScreenPresenterStub.java
@@ -18,6 +18,7 @@
 
 import android.support.annotation.FloatRange;
 import com.android.incallui.answer.protocol.AnswerScreenDelegate;
+import com.android.incallui.incalluilock.InCallUiLock;
 
 /**
  * Stub implementation of the answer screen delegate. Used to keep the answer fragment visible when
@@ -28,9 +29,6 @@
   public void onAnswerScreenUnready() {}
 
   @Override
-  public void onDismissDialog() {}
-
-  @Override
   public void onRejectCallWithMessage(String message) {}
 
   @Override
@@ -55,4 +53,9 @@
   public boolean isActionTimeout() {
     return false;
   }
+
+  @Override
+  public InCallUiLock acquireInCallUiLock(String tag) {
+    return InCallPresenter.getInstance().acquireInCallUiLock(tag);
+  }
 }
diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java
index b82b6c9..2ba4d98 100644
--- a/java/com/android/incallui/InCallActivity.java
+++ b/java/com/android/incallui/InCallActivity.java
@@ -245,23 +245,16 @@
       return true;
     }
 
-    if (common.hasPendingDialogs()) {
-      LogUtil.i(
-          "InCallActivity.shouldCloseActivityOnFinish", "dialog is visible, not closing activity");
-      return false;
-    }
-
-    AnswerScreen answerScreen = getAnswerScreen();
-    if (answerScreen != null && answerScreen.hasPendingDialogs()) {
+    if (InCallPresenter.getInstance().isInCallUiLocked()) {
       LogUtil.i(
           "InCallActivity.shouldCloseActivityOnFinish",
-          "answer screen dialog is visible, not closing activity");
+          "in call ui is locked, not closing activity");
       return false;
     }
 
     LogUtil.i(
         "InCallActivity.shouldCloseActivityOnFinish",
-        "activity is visible and has no dialogs, allowing activity to close");
+        "activity is visible and has no locks, allowing activity to close");
     return true;
   }
 
diff --git a/java/com/android/incallui/InCallActivityCommon.java b/java/com/android/incallui/InCallActivityCommon.java
index 9e6271f..9ccda32 100644
--- a/java/com/android/incallui/InCallActivityCommon.java
+++ b/java/com/android/incallui/InCallActivityCommon.java
@@ -60,6 +60,7 @@
 import com.android.incallui.call.DialerCall.State;
 import com.android.incallui.call.TelecomAdapter;
 import com.android.incallui.disconnectdialog.DisconnectMessage;
+import com.android.incallui.incalluilock.InCallUiLock;
 import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment;
 import com.android.incallui.telecomeventui.InternationalCallOnWifiDialogFragment.Callback;
 import java.lang.annotation.Retention;
@@ -337,6 +338,9 @@
     InCallPresenter.getInstance().onActivityStopped();
     if (!isRecreating) {
       InCallPresenter.getInstance().onUiShowing(false);
+      if (dialog != null) {
+        dialog.dismiss();
+      }
     }
     if (inCallActivity.isFinishing()) {
       InCallPresenter.getInstance().unsetActivity(inCallActivity);
@@ -581,11 +585,13 @@
     }
 
     this.dialog = dialog;
+    InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("showErrorDialog");
     dialog.setOnDismissListener(
         new OnDismissListener() {
           @Override
           public void onDismiss(DialogInterface dialog) {
             LogUtil.i("InCallActivityCommon.showErrorDialog", "dialog dismissed");
+            lock.release();
             onDialogDismissed();
           }
         });
@@ -596,7 +602,6 @@
   private void onDialogDismissed() {
     dialog = null;
     CallList.getInstance().onErrorDialogDismissed();
-    InCallPresenter.getInstance().onDismissDialog();
   }
 
   public void enableInCallOrientationEventListener(boolean enable) {
@@ -672,6 +677,7 @@
         (CheckBox) dialogCheckBoxView.findViewById(R.id.video_call_lte_to_wifi_failed_checkbox);
     wifiHandoverFailureCheckbox.setChecked(false);
 
+    InCallUiLock lock = InCallPresenter.getInstance().acquireInCallUiLock("WifiFailedDialog");
     dialog =
         builder
             .setView(dialogCheckBoxView)
@@ -694,6 +700,7 @@
                     onDialogDismissed();
                   }
                 })
+            .setOnDismissListener((dialog) -> lock.release())
             .create();
 
     LogUtil.i("InCallActivityCommon.showWifiFailedDialog", "as dialog");
diff --git a/java/com/android/incallui/InCallPresenter.java b/java/com/android/incallui/InCallPresenter.java
index 4cc03f3..a0069a6 100644
--- a/java/com/android/incallui/InCallPresenter.java
+++ b/java/com/android/incallui/InCallPresenter.java
@@ -22,6 +22,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Trace;
+import android.support.annotation.MainThread;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
@@ -34,6 +35,7 @@
 import android.telecom.VideoProfile;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
+import android.util.ArraySet;
 import android.view.Window;
 import android.view.WindowManager;
 import com.android.contacts.common.compat.CallCompat;
@@ -41,6 +43,7 @@
 import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler.OnCheckBlockedListener;
 import com.android.dialer.blocking.FilteredNumberCompat;
 import com.android.dialer.blocking.FilteredNumbersUtil;
+import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.DefaultDialerExecutorFactory;
 import com.android.dialer.enrichedcall.EnrichedCallComponent;
@@ -57,6 +60,7 @@
 import com.android.incallui.call.ExternalCallList;
 import com.android.incallui.call.TelecomAdapter;
 import com.android.incallui.disconnectdialog.DisconnectMessage;
+import com.android.incallui.incalluilock.InCallUiLock;
 import com.android.incallui.latencyreport.LatencyReport;
 import com.android.incallui.legacyblocking.BlockedNumberContentObserver;
 import com.android.incallui.spam.SpamCallListListener;
@@ -1190,18 +1194,6 @@
     return true;
   }
 
-  /**
-   * A dialog could have prevented in-call screen from being previously finished. This function
-   * checks to see if there should be any UI left and if not attempts to tear down the UI.
-   */
-  public void onDismissDialog() {
-    LogUtil.i("InCallPresenter.onDismissDialog", "Dialog dismissed");
-    if (mInCallState == InCallState.NO_CALLS) {
-      attemptFinishActivity();
-      attemptCleanup();
-    }
-  }
-
   /** Clears the previous fullscreen state. */
   public void clearFullscreen() {
     mIsFullScreen = false;
@@ -1491,7 +1483,10 @@
       mOrientationListeners.clear();
       mInCallEventListeners.clear();
       mInCallUiListeners.clear();
-
+      if (!mInCallUiLocks.isEmpty()) {
+        LogUtil.e("InCallPresenter.attemptCleanup", "held in call locks: " + mInCallUiLocks);
+        mInCallUiLocks.clear();
+      }
       LogUtil.d("InCallPresenter.attemptCleanup", "finished");
     }
   }
@@ -1784,4 +1779,61 @@
 
     void onUiShowing(boolean showing);
   }
+
+  private class InCallUiLockImpl implements InCallUiLock {
+    private final String tag;
+
+    private InCallUiLockImpl(String tag) {
+      this.tag = tag;
+    }
+
+    @MainThread
+    @Override
+    public void release() {
+      Assert.isMainThread();
+      releaseInCallUiLock(InCallUiLockImpl.this);
+    }
+
+    @Override
+    public String toString() {
+      return "InCallUiLock[" + tag + "]";
+    }
+  }
+
+  @MainThread
+  public InCallUiLock acquireInCallUiLock(String tag) {
+    Assert.isMainThread();
+    InCallUiLock lock = new InCallUiLockImpl(tag);
+    mInCallUiLocks.add(lock);
+    return lock;
+  }
+
+  @MainThread
+  private void releaseInCallUiLock(InCallUiLock lock) {
+    Assert.isMainThread();
+    LogUtil.i("InCallPresenter.releaseInCallUiLock", "releasing %s", lock);
+    mInCallUiLocks.remove(lock);
+    if (mInCallUiLocks.isEmpty()) {
+      LogUtil.i("InCallPresenter.releaseInCallUiLock", "all locks released");
+      if (mInCallState == InCallState.NO_CALLS) {
+        LogUtil.i("InCallPresenter.releaseInCallUiLock", "no more calls, finishing UI");
+        attemptFinishActivity();
+        attemptCleanup();
+      }
+    }
+  }
+
+  @MainThread
+  public boolean isInCallUiLocked() {
+    Assert.isMainThread();
+    if (mInCallUiLocks.isEmpty()) {
+      return false;
+    }
+    for (InCallUiLock lock : mInCallUiLocks) {
+      LogUtil.i("InCallPresenter.isInCallUiLocked", "still locked by %s", lock);
+    }
+    return true;
+  }
+
+  private final Set<InCallUiLock> mInCallUiLocks = new ArraySet<>();
 }
diff --git a/java/com/android/incallui/answer/impl/AnswerFragment.java b/java/com/android/incallui/answer/impl/AnswerFragment.java
index 18de72e..3476557 100644
--- a/java/com/android/incallui/answer/impl/AnswerFragment.java
+++ b/java/com/android/incallui/answer/impl/AnswerFragment.java
@@ -78,6 +78,7 @@
 import com.android.incallui.incall.protocol.PrimaryCallState;
 import com.android.incallui.incall.protocol.PrimaryInfo;
 import com.android.incallui.incall.protocol.SecondaryInfo;
+import com.android.incallui.incalluilock.InCallUiLock;
 import com.android.incallui.maps.MapsComponent;
 import com.android.incallui.sessiondata.AvatarPresenter;
 import com.android.incallui.sessiondata.MultimediaFragment;
@@ -977,6 +978,11 @@
   }
 
   @Override
+  public InCallUiLock acquireInCallUiLock(String tag) {
+    return answerScreenDelegate.acquireInCallUiLock(tag);
+  }
+
+  @Override
   public void smsSelected(@Nullable CharSequence text) {
     LogUtil.i("AnswerFragment.smsSelected", null);
     textResponsesFragment = null;
@@ -997,7 +1003,6 @@
   public void smsDismissed() {
     LogUtil.i("AnswerFragment.smsDismissed", null);
     textResponsesFragment = null;
-    answerScreenDelegate.onDismissDialog();
   }
 
   @Override
@@ -1014,7 +1019,6 @@
   public void customSmsDismissed() {
     LogUtil.i("AnswerFragment.customSmsDismissed", null);
     createCustomSmsDialogFragment = null;
-    answerScreenDelegate.onDismissDialog();
   }
 
   private boolean canRejectCallWithSms() {
diff --git a/java/com/android/incallui/answer/impl/CreateCustomSmsDialogFragment.java b/java/com/android/incallui/answer/impl/CreateCustomSmsDialogFragment.java
index b494092..73476f2 100644
--- a/java/com/android/incallui/answer/impl/CreateCustomSmsDialogFragment.java
+++ b/java/com/android/incallui/answer/impl/CreateCustomSmsDialogFragment.java
@@ -31,6 +31,7 @@
 import android.widget.Button;
 import android.widget.EditText;
 import com.android.dialer.common.FragmentUtils;
+import com.android.incallui.incalluilock.InCallUiLock;
 
 /**
  * Shows the dialog for users to enter a custom message when rejecting a call with an SMS message.
@@ -40,6 +41,7 @@
   private static final String ARG_ENTERED_TEXT = "enteredText";
 
   private EditText editText;
+  private InCallUiLock inCallUiLock;
 
   public static CreateCustomSmsDialogFragment newInstance() {
     return new CreateCustomSmsDialogFragment();
@@ -55,6 +57,11 @@
     if (savedInstanceState != null) {
       editText.setText(savedInstanceState.getCharSequence(ARG_ENTERED_TEXT));
     }
+
+    inCallUiLock =
+        FragmentUtils.getParentUnsafe(
+                CreateCustomSmsDialogFragment.this, CreateCustomSmsHolder.class)
+            .acquireInCallUiLock("CreateCustomSmsDialogFragment");
     builder
         .setCancelable(true)
         .setView(view)
@@ -124,12 +131,15 @@
   @Override
   public void onDismiss(DialogInterface dialogInterface) {
     super.onDismiss(dialogInterface);
+    inCallUiLock.release();
     FragmentUtils.getParentUnsafe(this, CreateCustomSmsHolder.class).customSmsDismissed();
   }
 
   /** Call back for {@link CreateCustomSmsDialogFragment} */
   public interface CreateCustomSmsHolder {
 
+    InCallUiLock acquireInCallUiLock(String tag);
+
     void customSmsCreated(@NonNull CharSequence text);
 
     void customSmsDismissed();
diff --git a/java/com/android/incallui/answer/impl/SmsBottomSheetFragment.java b/java/com/android/incallui/answer/impl/SmsBottomSheetFragment.java
index 085430e..6742e4a 100644
--- a/java/com/android/incallui/answer/impl/SmsBottomSheetFragment.java
+++ b/java/com/android/incallui/answer/impl/SmsBottomSheetFragment.java
@@ -36,6 +36,7 @@
 import com.android.dialer.common.DpUtil;
 import com.android.dialer.common.FragmentUtils;
 import com.android.dialer.common.LogUtil;
+import com.android.incallui.incalluilock.InCallUiLock;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -44,6 +45,8 @@
 
   private static final String ARG_OPTIONS = "options";
 
+  private InCallUiLock inCallUiLock;
+
   public static SmsBottomSheetFragment newInstance(@Nullable ArrayList<CharSequence> options) {
     SmsBottomSheetFragment fragment = new SmsBottomSheetFragment();
     Bundle args = new Bundle();
@@ -80,6 +83,10 @@
     LogUtil.i("SmsBottomSheetFragment.onCreateDialog", null);
     Dialog dialog = super.onCreateDialog(savedInstanceState);
     dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+
+    inCallUiLock =
+        FragmentUtils.getParentUnsafe(SmsBottomSheetFragment.this, SmsSheetHolder.class)
+            .acquireInCallUiLock("SmsBottomSheetFragment");
     return dialog;
   }
 
@@ -88,7 +95,7 @@
     Context context = new ContextThemeWrapper(getContext(), getTheme());
     TypedArray typedArray = context.obtainStyledAttributes(attrs);
     Drawable background = typedArray.getDrawable(0);
-    //noinspection ResourceType
+    // noinspection ResourceType
     typedArray.recycle();
 
     TextView textView = new TextView(context);
@@ -124,11 +131,14 @@
   public void onDismiss(DialogInterface dialogInterface) {
     super.onDismiss(dialogInterface);
     FragmentUtils.getParentUnsafe(this, SmsSheetHolder.class).smsDismissed();
+    inCallUiLock.release();
   }
 
   /** Callback interface for {@link SmsBottomSheetFragment} */
   public interface SmsSheetHolder {
 
+    InCallUiLock acquireInCallUiLock(String tag);
+
     void smsSelected(@Nullable CharSequence text);
 
     void smsDismissed();
diff --git a/java/com/android/incallui/answer/protocol/AnswerScreenDelegate.java b/java/com/android/incallui/answer/protocol/AnswerScreenDelegate.java
index 5d2c415..5710922 100644
--- a/java/com/android/incallui/answer/protocol/AnswerScreenDelegate.java
+++ b/java/com/android/incallui/answer/protocol/AnswerScreenDelegate.java
@@ -17,14 +17,13 @@
 package com.android.incallui.answer.protocol;
 
 import android.support.annotation.FloatRange;
+import com.android.incallui.incalluilock.InCallUiLock;
 
 /** Callbacks implemented by the container app for this module. */
 public interface AnswerScreenDelegate {
 
   void onAnswerScreenUnready();
 
-  void onDismissDialog();
-
   void onRejectCallWithMessage(String message);
 
   void onAnswer(boolean answerVideoAsAudio);
@@ -49,4 +48,6 @@
 
   /** Returns true if any answer/reject action timed out. */
   boolean isActionTimeout();
+
+  InCallUiLock acquireInCallUiLock(String tag);
 }
diff --git a/java/com/android/incallui/incalluilock/InCallUiLock.java b/java/com/android/incallui/incalluilock/InCallUiLock.java
new file mode 100644
index 0000000..fbeae61
--- /dev/null
+++ b/java/com/android/incallui/incalluilock/InCallUiLock.java
@@ -0,0 +1,29 @@
+/*
+ * 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.incallui.incalluilock;
+
+/**
+ * Prevents the {@link com.android.incallui.InCallActivity} from auto-finishing where there are no
+ * calls left. Acquired through {@link
+ * com.android.incallui.InCallPresenter#acquireInCallUiLock(String)}. Example: when a dialog is
+ * still being displayed to the user the InCallActivity should not disappear abruptly when the call
+ * ends, this lock should be held to keep the activity alive until it is dismissed.
+ */
+public interface InCallUiLock {
+
+  void release();
+}