DO NOT MERGE Workaround for java.lang.IllegalArgumentException in android.util.ArrayMap
am: a94a117971

* commit 'a94a1179714f6a79a6d9d7ae542458b8e6d742e7':
  DO NOT MERGE Workaround for java.lang.IllegalArgumentException in android.util.ArrayMap
diff --git a/src/com/android/incallui/Call.java b/src/com/android/incallui/Call.java
index 16a53b2..b768cdd 100644
--- a/src/com/android/incallui/Call.java
+++ b/src/com/android/incallui/Call.java
@@ -332,48 +332,7 @@
                             mTelecommCall.getChildren().get(i)).getId());
         }
 
-        Bundle callExtras = mTelecommCall.getDetails().getExtras();
-        if (callExtras != null) {
-            // Check for a change in the child address and notify any listeners.
-            if (callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS)) {
-                String childNumber = callExtras.getString(Connection.EXTRA_CHILD_ADDRESS);
-
-                if (!Objects.equals(childNumber, mChildNumber)) {
-                    mChildNumber = childNumber;
-                    CallList.getInstance().onChildNumberChange(this);
-                }
-            }
-
-            // Last forwarded number comes in as an array of strings.  We want to choose the last
-            // item in the array.  The forwarding numbers arrive independently of when the call is
-            // originally set up, so we need to notify the the UI of the change.
-            if (callExtras.containsKey(Connection.EXTRA_LAST_FORWARDED_NUMBER)) {
-                ArrayList<String> lastForwardedNumbers =
-                        callExtras.getStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER);
-
-                if (lastForwardedNumbers != null) {
-                    String lastForwardedNumber = null;
-                    if (!lastForwardedNumbers.isEmpty()) {
-                        lastForwardedNumber = lastForwardedNumbers.get(
-                                lastForwardedNumbers.size() - 1);
-                    }
-
-                    if (!Objects.equals(lastForwardedNumber, mLastForwardedNumber)) {
-                        mLastForwardedNumber = lastForwardedNumber;
-                        CallList.getInstance().onLastForwardedNumberChange(this);
-                    }
-                }
-            }
-
-            // Call subject is present in the extras at the start of call, so we do not need to
-            // notify any other listeners of this.
-            if (callExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)) {
-                String callSubject = callExtras.getString(Connection.EXTRA_CALL_SUBJECT);
-                if (!Objects.equals(mCallSubject, callSubject)) {
-                    mCallSubject = callSubject;
-                }
-            }
-        }
+        updateFromCallExtras(mTelecommCall.getDetails().getExtras());
 
         // If the handle of the call has changed, update state for the call determining if it is an
         // emergency call.
@@ -400,6 +359,77 @@
         }
     }
 
+    /**
+     * Tests corruption of the {@code callExtras} bundle by calling {@link
+     * Bundle#containsKey(String)}. If the bundle is corrupted a {@link IllegalArgumentException}
+     * will be thrown and caught by this function.
+     *
+     * @param callExtras the bundle to verify
+     * @returns {@code true} if the bundle is corrupted, {@code false} otherwise.
+     */
+    protected boolean areCallExtrasCorrupted(Bundle callExtras) {
+        /**
+         * There's currently a bug in Telephony service (b/25613098) that could corrupt the
+         * extras bundle, resulting in a IllegalArgumentException while validating data under
+         * {@link Bundle#containsKey(String)}.
+         */
+        try {
+            callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS);
+            return false;
+        } catch (IllegalArgumentException e) {
+            Log.e(this, "CallExtras is corrupted, ignoring exception", e);
+            return true;
+        }
+    }
+
+    protected void updateFromCallExtras(Bundle callExtras) {
+        if (callExtras == null || areCallExtrasCorrupted(callExtras)) {
+            /**
+             * If the bundle is corrupted, abandon information update as a work around. These are
+             * not critical for the dialer to function.
+             */
+            return;
+        }
+        // Check for a change in the child address and notify any listeners.
+        if (callExtras.containsKey(Connection.EXTRA_CHILD_ADDRESS)) {
+            String childNumber = callExtras.getString(Connection.EXTRA_CHILD_ADDRESS);
+            if (!Objects.equals(childNumber, mChildNumber)) {
+                mChildNumber = childNumber;
+                CallList.getInstance().onChildNumberChange(this);
+            }
+        }
+
+        // Last forwarded number comes in as an array of strings.  We want to choose the
+        // last item in the array.  The forwarding numbers arrive independently of when the
+        // call is originally set up, so we need to notify the the UI of the change.
+        if (callExtras.containsKey(Connection.EXTRA_LAST_FORWARDED_NUMBER)) {
+            ArrayList<String> lastForwardedNumbers =
+                    callExtras.getStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER);
+
+            if (lastForwardedNumbers != null) {
+                String lastForwardedNumber = null;
+                if (!lastForwardedNumbers.isEmpty()) {
+                    lastForwardedNumber = lastForwardedNumbers.get(
+                            lastForwardedNumbers.size() - 1);
+                }
+
+                if (!Objects.equals(lastForwardedNumber, mLastForwardedNumber)) {
+                    mLastForwardedNumber = lastForwardedNumber;
+                    CallList.getInstance().onLastForwardedNumberChange(this);
+                }
+            }
+        }
+
+        // Call subject is present in the extras at the start of call, so we do not need to
+        // notify any other listeners of this.
+        if (callExtras.containsKey(Connection.EXTRA_CALL_SUBJECT)) {
+            String callSubject = callExtras.getString(Connection.EXTRA_CALL_SUBJECT);
+            if (!Objects.equals(mCallSubject, callSubject)) {
+                mCallSubject = callSubject;
+            }
+        }
+    }
+
     private static int translateState(int state) {
         switch (state) {
             case android.telecom.Call.STATE_NEW:
diff --git a/tests/src/com/android/incallui/CallTest.java b/tests/src/com/android/incallui/CallTest.java
new file mode 100644
index 0000000..118ec38
--- /dev/null
+++ b/tests/src/com/android/incallui/CallTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2016 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;
+
+import android.os.Bundle;
+import android.telecom.Connection;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+// @formatter:off
+/**
+ * Run test with
+ * adb shell am instrument -e class com.android.incallui.CallTest -w com.google.android.dialer.tests/android.test.InstrumentationTestRunner
+ */
+// @formatter:on
+
+@SmallTest
+public class CallTest extends AndroidTestCase {
+
+    private TestCall mCall;
+
+    private final static String CHILD_NUMBER = "123";
+    private final static ArrayList<String> LAST_FORWARDED_NUMBER_LIST =
+            new ArrayList(Arrays.asList("456", "789"));
+    private final static String LAST_FORWARDED_NUMBER = "789";
+    private final static String CALL_SUBJECT = "foo";
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mCall = new TestCall();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testUpdateFromCallExtras() {
+        mCall.updateFromCallExtras(getTestBundle());
+        verifyTestBundleResult();
+    }
+
+    public void testUpdateFromCallExtras_corruptedBundle() {
+        mCall.setBundleCorrupted(true);
+        mCall.updateFromCallExtras(getTestBundle());
+
+        assertEquals(mCall.getChildNumber(), null);
+        assertEquals(mCall.getLastForwardedNumber(), null);
+        assertEquals(mCall.getCallSubject(), null);
+    }
+
+    public void testUpdateFromCallExtras_corruptedBundleOverwrite() {
+
+        mCall.updateFromCallExtras(getTestBundle());
+        mCall.setBundleCorrupted(true);
+        Bundle bundle = new Bundle();
+        bundle.putString(Connection.EXTRA_CHILD_ADDRESS, "321");
+        bundle.putStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER,
+                new ArrayList(Arrays.asList("654", "987")));
+        bundle.putString(Connection.EXTRA_CALL_SUBJECT, "bar");
+        mCall.updateFromCallExtras(bundle);
+        //corrupted bundle should not overwrite existing values.
+        verifyTestBundleResult();
+    }
+
+    private Bundle getTestBundle() {
+        Bundle bundle = new Bundle();
+        bundle.putString(Connection.EXTRA_CHILD_ADDRESS, CHILD_NUMBER);
+        bundle.putStringArrayList(Connection.EXTRA_LAST_FORWARDED_NUMBER,
+                LAST_FORWARDED_NUMBER_LIST);
+        bundle.putString(Connection.EXTRA_CALL_SUBJECT, CALL_SUBJECT);
+        return bundle;
+    }
+
+    private void verifyTestBundleResult() {
+        assertEquals(CHILD_NUMBER, mCall.getChildNumber());
+        assertEquals(LAST_FORWARDED_NUMBER, mCall.getLastForwardedNumber());
+        assertEquals(CALL_SUBJECT, mCall.getCallSubject());
+    }
+
+    private class TestCall extends Call {
+
+        private boolean mBundleCorrupted = false;
+
+        public TestCall() {
+            super(Call.State.NEW);
+        }
+
+        @Override
+        public void updateFromCallExtras(Bundle bundle) {
+            super.updateFromCallExtras(bundle);
+        }
+
+        public void setBundleCorrupted(boolean value) {
+            this.mBundleCorrupted = value;
+        }
+
+        @Override
+        protected boolean areCallExtrasCorrupted(Bundle callExtras) {
+            if (mBundleCorrupted) {
+                return true;
+            }
+            return super.areCallExtrasCorrupted(callExtras);
+        }
+    }
+}