Add debounce to start of phone call ops

Wait 250ms before starting PHONE_CALL ops. This gives time for apps like
dialer or Fi to mute the microphone or take over the call.
Test: manual
Fixes: 192076383

Change-Id: I729c318aada005f41d6fd43d52e1d18748c33e25
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 9765c0e..c207940 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -45,6 +45,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.DeviceConfig;
 import android.telecom.CallAudioState;
 import android.telecom.ConnectionService;
 import android.telecom.InCallService;
@@ -83,8 +84,8 @@
  */
 public class InCallController extends CallsManagerListenerBase implements
         AppOpsManager.OnOpActiveChangedListener {
-    public static final int IN_CALL_SERVICE_NOTIFICATION_ID = 3;
     public static final String NOTIFICATION_TAG = InCallController.class.getSimpleName();
+    public static final int IN_CALL_SERVICE_NOTIFICATION_ID = 3;
 
     public class InCallServiceConnection {
         /**
@@ -1006,6 +1007,14 @@
     private boolean mIsCallUsingMicrophone = false;
 
     /**
+     * {@code true} if InCallController is tracking a managed, not external call which is using the
+     * microphone, {@code false} otherwise.
+     */
+    private boolean mIsTrackingManagedAliveCall = false;
+
+    private boolean mIsStartCallDelayScheduled = false;
+
+    /**
      * A list of call IDs which are currently using the camera.
      */
     private ArrayList<String> mCallsUsingCamera = new ArrayList<>();
@@ -2210,16 +2219,38 @@
         Log.i(this, "trackCallingUserInterfaceStopped: %s is no longer calling UX", packageName);
     }
 
+    private void maybeTrackMicrophoneUse(boolean isMuted) {
+        maybeTrackMicrophoneUse(isMuted, false);
+    }
+
     /**
      * As calls are added, removed and change between external and non-external status, track
      * whether the current active calling UX is using the microphone.  We assume if there is a
      * managed call present and the mic is not muted that the microphone is in use.
      */
-    private void maybeTrackMicrophoneUse(boolean isMuted) {
-        boolean wasTrackingManagedCall = mIsCallUsingMicrophone;
-        mIsCallUsingMicrophone = isTrackingManagedAliveCall() && !isMuted
-                && !carrierPrivilegedUsingMicDuringVoipCall();
-        if (wasTrackingManagedCall != mIsCallUsingMicrophone) {
+    private void maybeTrackMicrophoneUse(boolean isMuted, boolean isScheduledDelay) {
+        if (mIsStartCallDelayScheduled && !isScheduledDelay) {
+            return;
+        }
+
+        mIsStartCallDelayScheduled = false;
+        boolean wasUsingMicrophone = mIsCallUsingMicrophone;
+        boolean wasTrackingCall = mIsTrackingManagedAliveCall;
+        mIsTrackingManagedAliveCall = isTrackingManagedAliveCall();
+        if (!wasTrackingCall && mIsTrackingManagedAliveCall) {
+            mIsStartCallDelayScheduled = true;
+            mHandler.postDelayed(new Runnable("ICC.mTMU", mLock) {
+                @Override
+                public void loggedRun() {
+                    maybeTrackMicrophoneUse(isMuted(), true);
+                }
+            }.prepare(), mTimeoutsAdapter.getCallStartAppOpDebounceIntervalMillis());
+            return;
+        }
+
+        mIsCallUsingMicrophone = mIsTrackingManagedAliveCall && !isMuted
+                && !isCarrierPrivilegedUsingMicDuringVoipCall();
+        if (wasUsingMicrophone != mIsCallUsingMicrophone) {
             if (mIsCallUsingMicrophone) {
                 mAppOpsManager.startOp(AppOpsManager.OP_PHONE_CALL_MICROPHONE, myUid(),
                         mContext.getOpPackageName(), false, null, null);
@@ -2240,7 +2271,7 @@
                 c.getState()));
     }
 
-    private boolean carrierPrivilegedUsingMicDuringVoipCall() {
+    private boolean isCarrierPrivilegedUsingMicDuringVoipCall() {
         return !mActiveCarrierPrivilegedApps.isEmpty() &&
                 mCallIdMapper.getCalls().stream().anyMatch(Call::getIsVoipAudioMode);
     }
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 4f35003..36caa25 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom;
 
 import android.content.ContentResolver;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.telecom.CallDiagnosticService;
 import android.telecom.CallRedirectionService;
@@ -75,6 +76,10 @@
         public long getCallDiagnosticServiceTimeoutMillis(ContentResolver cr) {
             return Timeouts.getCallDiagnosticServiceTimeoutMillis(cr);
         }
+
+        public long getCallStartAppOpDebounceIntervalMillis() {
+            return  Timeouts.getCallStartAppOpDebounceIntervalMillis();
+        }
     }
 
     /** A prefix to use for all keys so to not clobber the global namespace. */
@@ -232,6 +237,10 @@
         return get(contentResolver, "call_diagnostic_service_timeout", 2000L /* 2 sec */);
     }
 
+    public static long getCallStartAppOpDebounceIntervalMillis() {
+        return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PRIVACY, "app_op_debounce_time", 250L);
+    }
+
     /**
      * Returns the number of milliseconds for which the system should exempt the default dialer from
      * power save restrictions due to the dialer needing to handle a missed call notification