Introduce the ability to dump the wbxml from EAS responses.

Similar to CurlLogger that dumps the base64 represention of
the EAS request, WbxmlResponseLogger will dump the base64
representation of responses from the server so that we can
easily view the XML payload. Unit tests included.

Change-Id: I96e2c2d508ac27002125ee83d307ff7cd75400c7
(cherry picked from commit 17403f2cc87358e7b768cb982921d28694a7932d)
diff --git a/src/com/android/exchange/service/EasServerConnection.java b/src/com/android/exchange/service/EasServerConnection.java
index 2fb4ebf..64b932a 100644
--- a/src/com/android/exchange/service/EasServerConnection.java
+++ b/src/com/android/exchange/service/EasServerConnection.java
@@ -39,6 +39,7 @@
 import com.android.exchange.EasResponse;
 import com.android.exchange.eas.EasConnectionCache;
 import com.android.exchange.utility.CurlLogger;
+import com.android.exchange.utility.WbxmlResponseLogger;
 import com.android.mail.utils.LogUtils;
 
 import org.apache.http.HttpEntity;
@@ -181,6 +182,7 @@
                 protected BasicHttpProcessor createHttpProcessor() {
                     final BasicHttpProcessor processor = super.createHttpProcessor();
                     processor.addRequestInterceptor(new CurlLogger());
+                    processor.addResponseInterceptor(new WbxmlResponseLogger());
                     return processor;
                 }
             };
diff --git a/src/com/android/exchange/utility/WbxmlResponseLogger.java b/src/com/android/exchange/utility/WbxmlResponseLogger.java
new file mode 100644
index 0000000..fd9ecbb
--- /dev/null
+++ b/src/com/android/exchange/utility/WbxmlResponseLogger.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2014 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.exchange.utility;
+
+import android.util.Base64;
+import android.util.Log;
+
+import com.android.exchange.Eas;
+import com.android.mail.utils.LogUtils;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.entity.BufferedHttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpResponseInterceptor;
+import org.apache.http.protocol.HttpContext;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * Dumps the wbxml in base64 (much like {@link CurlLogger}) so that the
+ * response from Exchange can be viewed for debugging purposes.
+ */
+public class WbxmlResponseLogger implements HttpResponseInterceptor {
+    private static final String TAG = Eas.LOG_TAG;
+    protected static final int MAX_LENGTH = 1024;
+
+    protected static boolean shouldLogResponse(final long contentLength) {
+        // Not going to bother if there is a lot of content since most of that information
+        // will probably just be message contents anyways.
+        return contentLength < MAX_LENGTH;
+    }
+
+    protected static String processContentEncoding(final Header encodingHeader) {
+        if (encodingHeader != null) {
+            final String encodingValue = encodingHeader.getValue();
+            return (encodingValue == null) ? "UTF-8" : encodingValue;
+        }
+        return "UTF-8";
+    }
+
+    protected static byte[] getContentAsByteArray(InputStream is, int batchSize)
+        throws IOException {
+        // Start building our byte array to encode and dump.
+        int count;
+        final byte[] data = new byte[batchSize];
+        final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        while ((count = is.read(data, 0, data.length)) != -1) {
+            buffer.write(data, 0, count);
+        }
+        buffer.flush();
+        return buffer.toByteArray();
+    }
+
+    @Override
+    public void process(HttpResponse response, HttpContext context) throws IOException {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            // Wrap the HttpEntity so the response InputStream can be requested and processed
+            // numerous times.
+            response.setEntity(new BufferedHttpEntity(response.getEntity()));
+
+            // Now grab the wrapped HttpEntity so that you safely can process the response w/o
+            // affecting the core response processing module.
+            final HttpEntity entity = response.getEntity();
+            if (!shouldLogResponse(entity.getContentLength())) {
+                LogUtils.d(TAG, "wbxml response: [TOO MUCH DATA TO INCLUDE]");
+                return;
+            }
+
+            // We need to figure out the encoding in the case that it is gzip and we need to
+            // inflate it during processing.
+            final Header encodingHeader = entity.getContentEncoding();
+            final String encoding = processContentEncoding(encodingHeader);
+
+            final InputStream is;
+            if (encoding.equals("gzip")) {
+                // We need to inflate this first.
+                final InputStream unwrappedIs = response.getEntity().getContent();
+                is = new GZIPInputStream(unwrappedIs);
+            } else {
+                is = response.getEntity().getContent();
+            }
+
+            final byte currentXMLBytes[] = getContentAsByteArray(is, MAX_LENGTH);
+
+            // Now let's dump out the base 64 encoded bytes and the rest of the command that will
+            // tell us what the response is.
+            final String base64 = Base64.encodeToString(currentXMLBytes, Base64.NO_WRAP);
+            LogUtils.d(TAG, "wbxml response: echo '%s' | base64 -d | wbxml", base64);
+        }
+    }
+
+}
diff --git a/tests/src/com/android/exchange/utility/WbxmlResponseLoggerTests.java b/tests/src/com/android/exchange/utility/WbxmlResponseLoggerTests.java
new file mode 100644
index 0000000..29d493b
--- /dev/null
+++ b/tests/src/com/android/exchange/utility/WbxmlResponseLoggerTests.java
@@ -0,0 +1,83 @@
+/* Copyright (C) 2014 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.exchange.utility;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.apache.http.message.BasicHeader;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Test for {@link WbxmlResponseLogger}.
+ * You can run this entire test case with:
+ *   runtest -c com.android.exchange.utility.WbxmlResponseLoggerTests exchange
+ */
+@SmallTest
+public class WbxmlResponseLoggerTests extends TestCase {
+    private static final byte testArray[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x11,};
+
+    public void testShouldLogResponseTooBig() {
+        final long contentSize = WbxmlResponseLogger.MAX_LENGTH + 1;
+        assertEquals(false, WbxmlResponseLogger.shouldLogResponse(contentSize));
+    }
+
+    public void testShouldLogResponseSmallEnough() {
+        final long contentSize = WbxmlResponseLogger.MAX_LENGTH - 1;
+        assertEquals(true, WbxmlResponseLogger.shouldLogResponse(contentSize));
+    }
+
+    public void testProcessContentEncoding() {
+        final String encoding = "US-ASCII";
+        final BasicHeader header = new BasicHeader("content-encoding", encoding);
+        final String outputEncoding = WbxmlResponseLogger.processContentEncoding(header);
+        assertEquals(true, encoding.equals(outputEncoding));
+    }
+
+    public void testProcessContentEncodingNullHeader() {
+        final String encoding = "UTF-8";
+        final String outputEncoding = WbxmlResponseLogger.processContentEncoding(null);
+        assertEquals(true, encoding.equals(outputEncoding));
+    }
+
+    public void testProcessContentEncodingNullValue() {
+        final String encoding = "UTF-8";
+        final BasicHeader header = new BasicHeader("content-encoding", null);
+        final String outputEncoding = WbxmlResponseLogger.processContentEncoding(header);
+        assertEquals(true, encoding.equals(outputEncoding));
+    }
+
+    public void testGetContentAsByteArraySingleBatch() throws IOException {
+        final ByteArrayInputStream bis = new ByteArrayInputStream(testArray);
+        final byte outputBytes[] = WbxmlResponseLogger.getContentAsByteArray(bis,
+            testArray.length);
+        assertEquals(true, Arrays.equals(testArray, outputBytes));
+    }
+
+    public void testGetContentAsByteArrayMultipleBatches() throws IOException {
+        final ByteArrayInputStream bis = new ByteArrayInputStream(testArray);
+        // If we cut the batch size to be half the length of testArray, we force
+        // 2 batches of processing.
+        final byte outputBytes[] = WbxmlResponseLogger.getContentAsByteArray(bis,
+                testArray.length / 2);
+        assertEquals(true, Arrays.equals(testArray, outputBytes));
+    }
+}