Merge "Temp work around for audio bugs" into master-nova
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index 96d8374..f40d4ea 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -23,6 +23,7 @@
 import com.google.common.base.Preconditions;
 
 import java.util.Date;
+import java.util.Locale;
 import java.util.UUID;
 
 /**
@@ -88,7 +89,8 @@
 
     /** {@inheritDoc} */
     @Override public String toString() {
-        return "[" + mId + ", " + mState + ", " + mCallService.getComponentName() + "]";
+        return String.format(Locale.US, "[%s, %s, %s, %s]", mId, mState,
+                mCallService.getComponentName(), Log.pii(mHandle));
     }
 
     String getId() {
@@ -167,14 +169,14 @@
     }
 
     /**
-     * Aborts ongoing attempts to connect this call. No-op once the call is connected or has been
-     * disconnected.  See {@link #disconnect} for already-connected calls.
+     * Aborts ongoing attempts to connect this call. Only applicable to {@link CallState#NEW}
+     * outgoing calls.  See {@link #disconnect} for already-connected calls.
      */
     void abort() {
-        if (mState == CallState.NEW ||
-                mState == CallState.DIALING ||
-                mState == CallState.RINGING) {
-
+        if (mState == CallState.NEW) {
+            if (mCallService != null) {
+                mCallService.abort(mId);
+            }
             clearCallService();
             clearCallServiceSelector();
             mState = CallState.ABORTED;
diff --git a/src/com/android/telecomm/CallServiceAdapter.java b/src/com/android/telecomm/CallServiceAdapter.java
index f602d50..8daa16e 100644
--- a/src/com/android/telecomm/CallServiceAdapter.java
+++ b/src/com/android/telecomm/CallServiceAdapter.java
@@ -46,9 +46,16 @@
     /** Used to run code (e.g. messages, Runnables) on the main (UI) thread. */
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
-    /** The set of pending incoming call IDs. Contains the call IDs for which we are expecting
-     * details via {@link #handleIncomingCall}. If {@link #handleIncomingCall} is invoked for a call
-     * ID that is not in this set, it will be ignored.
+    /**
+     * The set of pending outgoing call IDs.  Any {@link #handleSuccessfulOutgoingCall} and
+     * {@link #handleFailedOutgoingCall} invocations with a call ID that is not in this set
+     * are ignored.
+     */
+    private final Set<String> mPendingOutgoingCallIds = Sets.newHashSet();
+
+    /**
+     * The set of pending incoming call IDs.  Any {@link #handleIncomingCall} invocations with
+     * a call ID not in this set are ignored.
      */
     private final Set<String> mPendingIncomingCallIds = Sets.newHashSet();
 
@@ -80,7 +87,7 @@
                     mIncomingCallsManager.handleSuccessfulIncomingCall(callInfo);
                 } else {
                     Log.wtf(CallServiceAdapter.this,
-                            "Received details for an unknown incoming call %s", callInfo);
+                            "Unknown incoming call: %s", callInfo);
                 }
             }
         });
@@ -91,7 +98,13 @@
         checkValidCallId(callId);
         mHandler.post(new Runnable() {
             @Override public void run() {
-                mOutgoingCallsManager.handleSuccessfulCallAttempt(callId);
+                if (mPendingOutgoingCallIds.remove(callId)) {
+                    mOutgoingCallsManager.handleSuccessfulCallAttempt(callId);
+                } else {
+                    // TODO(gilad): Figure out how to wire up the callService.abort() call.
+                    Log.wtf(CallServiceAdapter.this,
+                            "Unknown outgoing call: %s", callId);
+                }
             }
         });
     }
@@ -101,7 +114,12 @@
         checkValidCallId(callId);
         mHandler.post(new Runnable() {
             @Override public void run() {
-                mOutgoingCallsManager.handleFailedCallAttempt(callId, reason);
+                if (mPendingOutgoingCallIds.remove(callId)) {
+                    mOutgoingCallsManager.handleFailedCallAttempt(callId, reason);
+                } else {
+                    Log.wtf(CallServiceAdapter.this,
+                            "Unknown outgoing call: %s", callId);
+                }
             }
         });
     }
@@ -137,6 +155,8 @@
     }
 
     /** {@inheritDoc} */
+    // TODO(gilad): Ensure that any communication from the underlying ICallService
+    // implementation is expected (or otherwise suppressed at the adapter level).
     @Override public void setDisconnected(final String callId) {
         checkValidCallId(callId);
         mHandler.post(new Runnable() {
@@ -147,6 +167,26 @@
     }
 
     /**
+     * Adds the specified call ID to the list of pending outgoing call IDs.
+     * TODO(gilad): Consider passing the call processor (instead of the ID) both here and in the
+     * remove case (same for incoming) such that the detour via the *CallsManager can be avoided.
+     *
+     * @param callId The ID of the call.
+     */
+    void addPendingOutgoingCallId(String callId) {
+        mPendingOutgoingCallIds.add(callId);
+    }
+
+    /**
+     * Removes the specified call ID from the list of pending outgoing call IDs.
+     *
+     * @param callId The ID of the call.
+     */
+    void removePendingOutgoingCallId(String callId) {
+        mPendingOutgoingCallIds.remove(callId);
+    }
+
+    /**
      * Adds a call ID to the list of pending incoming call IDs. Only calls with call IDs in the
      * list will be handled by {@link #handleIncomingCall}.
      *
@@ -157,7 +197,7 @@
     }
 
     /**
-     * Removed a call ID from the list of pending incoming call IDs.
+     * Removes the specified call ID from the list of pending incoming call IDs.
      *
      * @param callId The ID of the call.
      */
diff --git a/src/com/android/telecomm/CallServiceWrapper.java b/src/com/android/telecomm/CallServiceWrapper.java
index 94be7c8..15ec20d 100644
--- a/src/com/android/telecomm/CallServiceWrapper.java
+++ b/src/com/android/telecomm/CallServiceWrapper.java
@@ -24,6 +24,7 @@
 import android.telecomm.CallServiceDescriptor;
 import android.telecomm.ICallService;
 import android.telecomm.ICallServiceAdapter;
+import android.telecomm.ICallServiceProvider;
 
 /**
  * Wrapper for {@link ICallService}s, handles binding to {@link ICallService} and keeps track of
@@ -92,11 +93,25 @@
 
     /** See {@link ICallService#call}. */
     public void call(CallInfo callInfo) {
+        String callId = callInfo.getId();
         if (isServiceValid("call")) {
             try {
                 mServiceInterface.call(callInfo);
+                mAdapter.addPendingOutgoingCallId(callId);
             } catch (RemoteException e) {
-                Log.e(this, e, "Failed to place call " + callInfo.getId() + ".");
+                Log.e(this, e, "Failed to place call " + callId + ".");
+            }
+        }
+    }
+
+    /** See {@link ICallService#abort}. */
+    public void abort(String callId) {
+        mAdapter.removePendingOutgoingCallId(callId);
+        if (isServiceValid("abort")) {
+            try {
+                mServiceInterface.abort(callId);
+            } catch (RemoteException e) {
+                Log.e(this, e, "Failed to abort call %s", callId);
             }
         }
     }
diff --git a/src/com/android/telecomm/Log.java b/src/com/android/telecomm/Log.java
index 73892cd..f70cf55 100644
--- a/src/com/android/telecomm/Log.java
+++ b/src/com/android/telecomm/Log.java
@@ -18,6 +18,8 @@
 
 import java.util.IllegalFormatException;
 import java.util.Locale;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 
 /**
  * Manages logging for the entire module.
@@ -118,6 +120,44 @@
         android.util.Log.wtf(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
     }
 
+    /**
+     * Redact personally identifiable information for production users.
+     * If we are running in verbose mode, return the original string, otherwise
+     * return a SHA-1 hash of the input string.
+     */
+    public static String pii(Object pii) {
+        if (pii == null || VERBOSE) {
+            return String.valueOf(pii);
+        }
+        return "[" + secureHash(String.valueOf(pii).getBytes()) + "]";
+    }
+
+    private static String secureHash(byte[] input) {
+        MessageDigest messageDigest;
+        try {
+            messageDigest = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException e) {
+            return null;
+        }
+        messageDigest.update(input);
+        byte[] result = messageDigest.digest();
+        return encodeHex(result);
+    }
+
+    private static String encodeHex(byte[] bytes) {
+        StringBuffer hex = new StringBuffer(bytes.length * 2);
+
+        for (int i = 0; i < bytes.length; i++) {
+            int byteIntValue = (int) bytes[i] & 0xff;
+            if (byteIntValue < 0x10) {
+                hex.append("0");
+            }
+            hex.append(Integer.toString(byteIntValue, 16));
+        }
+
+        return hex.toString();
+    }
+
     private static String getPrefixFromObject(Object obj) {
         return obj == null ? "<null>" : obj.getClass().getSimpleName();
     }
diff --git a/src/com/android/telecomm/OutgoingCallProcessor.java b/src/com/android/telecomm/OutgoingCallProcessor.java
index 42a16c6..4d21280 100644
--- a/src/com/android/telecomm/OutgoingCallProcessor.java
+++ b/src/com/android/telecomm/OutgoingCallProcessor.java
@@ -68,7 +68,7 @@
     private final Map<String, CallServiceWrapper> mCallServicesById = Maps.newHashMap();
 
     /**
-     * The set of currently-available call-service selector implementations.
+     * The list of currently-available call-service selector implementations.
      */
     private final List<ICallServiceSelector> mSelectors;
 
@@ -111,9 +111,6 @@
 
         ThreadUtil.checkOnMainThread();
 
-        Preconditions.checkNotNull(callServices);
-        Preconditions.checkNotNull(selectors);
-
         mCall = call;
         mSelectors = selectors;
         mOutgoingCallsManager = outgoingCallsManager;
@@ -132,17 +129,14 @@
      * Initiates the attempt to place the call.  No-op beyond the first invocation.
      */
     void process() {
-        ThreadUtil.checkOnMainThread();
+        if (!mIsAborted) {
+            // Only process un-aborted calls.
+            ThreadUtil.checkOnMainThread();
 
-        if (mSelectors.isEmpty() || mCallServiceDescriptors.isEmpty()) {
-            // TODO(gilad): Consider adding a failure message/type to differentiate the various
-            // cases, or potentially throw an exception in this case.
-            // TODO(gilad): Perform this check all the way up in switchboard to short-circuit
-            // the current detour.
-            mOutgoingCallsManager.handleFailedOutgoingCall(mCall);
-        } else if (mSelectorIterator == null) {
-            mSelectorIterator = mSelectors.iterator();
-            attemptNextSelector();
+            if (mSelectorIterator == null) {
+                mSelectorIterator = mSelectors.iterator();
+                attemptNextSelector();
+            }
         }
     }
 
@@ -154,9 +148,6 @@
         ThreadUtil.checkOnMainThread();
         if (!mIsAborted) {
             mCall.abort();
-
-            // TODO(gilad): Add logic to notify the relevant call service and/or selector.
-
             mIsAborted = true;
         }
     }
@@ -185,11 +176,13 @@
      * @param reason The call-service supplied reason for the failed call attempt.
      */
     void handleFailedCallAttempt(String reason) {
-        ThreadUtil.checkOnMainThread();
+        if (!mIsAborted) {
+            ThreadUtil.checkOnMainThread();
 
-        mCall.clearCallService();
-        mCall.clearCallServiceSelector();
-        attemptNextCallService();
+            mCall.clearCallService();
+            mCall.clearCallServiceSelector();
+            attemptNextCallService();
+        }
     }
 
     /**