Allow sync data to arrive via the chunked transfer encoding.

* Fixes #2216885
* The bug is that the sync adapters weren't set up to handle chunked
  encoding, primarily because 1) I hadn't seen any servers use it, and
  2) when we changed from HttpUrlConnection to HttpClient, support for
  chunked wasn't added (HttpUrlConnection didn't support it)
* The fix for xml data is trivial, since the Content-Length returned in
  the chunked case (-1) was being disallowed, but works perfectly well
  with HttpClient.
* The fix for attachments is less trivial, but still straightforward.
* With this change, we are no longer dependent on receiving content-length,
  which is highly desirable

Change-Id: Ie3bd6af0cf68f3afa190711d96b1dbd2e6341f79
diff --git a/src/com/android/exchange/EasSyncService.java b/src/com/android/exchange/EasSyncService.java
index 81fd4ac..f15c181 100644
--- a/src/com/android/exchange/EasSyncService.java
+++ b/src/com/android/exchange/EasSyncService.java
@@ -314,19 +314,44 @@
                     destDir.mkdirs();
                 }
                 FileOutputStream os = new FileOutputStream(f);
-                if (len > 0) {
+                // len > 0 means that Content-Length was set in the headers
+                // len < 0 means "chunked" transfer-encoding
+                if (len != 0) {
                     try {
                         mPendingPartRequest = req;
                         byte[] bytes = new byte[CHUNK_SIZE];
                         int length = len;
-                        while (len > 0) {
-                            int n = (len > CHUNK_SIZE ? CHUNK_SIZE : len);
-                            int read = is.read(bytes, 0, n);
+                        // Loop terminates 1) when EOF is reached or 2) if an IOException occurs
+                        // One of these is guaranteed to occur
+                        int totalRead = 0;
+                        userLog("Attachment content-length: ", len);
+                        while (true) {
+                            int read = is.read(bytes, 0, CHUNK_SIZE);
+
+                            // read < 0 means that EOF was reached
+                            if (read < 0) {
+                                userLog("Attachment load reached EOF, totalRead: ", totalRead);
+                                break;
+                            }
+
+                            // Keep track of how much we've read for progress callback
+                            totalRead += read;
+
+                            // Write these bytes out
                             os.write(bytes, 0, read);
-                            len -= read;
-                            int pct = ((length - len) * 100 / length);
-                            doProgressCallback(msg.mId, att.mId, pct);
-                        }
+
+                            // We can't report percentages if this is chunked; by definition, the
+                            // length of incoming data is unknown
+                            if (length > 0) {
+                                // Belt and suspenders check to prevent runaway reading
+                                if (totalRead > length) {
+                                    errorLog("totalRead is greater than attachment length?");
+                                    break;
+                                }
+                                int pct = (totalRead * 100 / length);
+                                doProgressCallback(msg.mId, att.mId, pct);
+                            }
+                       }
                     } finally {
                         mPendingPartRequest = null;
                     }
@@ -562,7 +587,7 @@
                  if (code == HttpStatus.SC_OK) {
                      HttpEntity entity = resp.getEntity();
                      int len = (int)entity.getContentLength();
-                     if (len > 0) {
+                     if (len != 0) {
                          InputStream is = entity.getContent();
                          // Returns true if we need to sync again
                          if (new FolderSyncParser(is, new AccountSyncAdapter(mMailbox, this))
@@ -747,7 +772,7 @@
                         HttpEntity e = res.getEntity();
                         int len = (int)e.getContentLength();
                         InputStream is = res.getEntity().getContent();
-                        if (len > 0) {
+                        if (len != 0) {
                             int pingResult = parsePingResult(is, mContentResolver, pingErrorMap);
                             // If our ping completed (status = 1), and we weren't forced and we're
                             // not at the maximum, try increasing timeout by two minutes
@@ -1019,7 +1044,7 @@
             HttpResponse resp = sendHttpClientPost("Sync", s.toByteArray());
             int code = resp.getStatusLine().getStatusCode();
             if (code == HttpStatus.SC_OK) {
-                 InputStream is = resp.getEntity().getContent();
+                InputStream is = resp.getEntity().getContent();
                 if (is != null) {
                     moreAvailable = target.parse(is);
                     target.cleanup();