1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.email.provider; 18 19 import android.content.ContentResolver; 20 import android.content.Context; 21 import android.database.Cursor; 22 import android.database.CursorWrapper; 23 import android.net.Uri; 24 import android.provider.BaseColumns; 25 import android.util.SparseArray; 26 27 import com.android.emailcommon.provider.EmailContent.Body; 28 import com.android.mail.utils.HtmlSanitizer; 29 import com.android.mail.utils.LogUtils; 30 31 import org.apache.commons.io.IOUtils; 32 33 import java.io.IOException; 34 import java.io.InputStream; 35 36 /** 37 * This class wraps a cursor for the purpose of bypassing the CursorWindow object for the 38 * potentially over-sized body content fields. The CursorWindow has a hard limit of 2MB and so a 39 * large email message can exceed that limit and cause the cursor to fail to load. 40 * 41 * To get around this, we load null values in those columns, and then in this wrapper we directly 42 * load the content from the provider, skipping the cursor window. 43 * 44 * This will still potentially blow up if this cursor gets wrapped in a CrossProcessCursorWrapper 45 * which uses a CursorWindow to shuffle results between processes. Since we're only using this for 46 * passing a cursor back to UnifiedEmail this shouldn't be an issue. 47 */ 48 public class EmailMessageCursor extends CursorWrapper { 49 50 private final SparseArray<String> mTextParts; 51 private final SparseArray<String> mHtmlParts; 52 private final int mTextColumnIndex; 53 private final int mHtmlColumnIndex; 54 55 public EmailMessageCursor(final Context c, final Cursor cursor, final String htmlColumn, 56 final String textColumn) { 57 super(cursor); 58 mHtmlColumnIndex = cursor.getColumnIndex(htmlColumn); 59 mTextColumnIndex = cursor.getColumnIndex(textColumn); 60 final int cursorSize = cursor.getCount(); 61 mHtmlParts = new SparseArray<String>(cursorSize); 62 mTextParts = new SparseArray<String>(cursorSize); 63 64 final ContentResolver cr = c.getContentResolver(); 65 66 while (cursor.moveToNext()) { 67 final int position = cursor.getPosition(); 68 final long messageId = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID)); 69 try { 70 if (mHtmlColumnIndex != -1) { 71 final Uri htmlUri = Body.getBodyHtmlUriForMessageWithId(messageId); 72 final InputStream in = cr.openInputStream(htmlUri); 73 final String underlyingHtmlString; 74 try { 75 underlyingHtmlString = IOUtils.toString(in); 76 } finally { 77 in.close(); 78 } 79 final String sanitizedHtml = HtmlSanitizer.sanitizeHtml(underlyingHtmlString); 80 mHtmlParts.put(position, sanitizedHtml); 81 } 82 } catch (final IOException e) { 83 LogUtils.v(LogUtils.TAG, e, "Did not find html body for message %d", messageId); 84 } 85 try { 86 if (mTextColumnIndex != -1) { 87 final Uri textUri = Body.getBodyTextUriForMessageWithId(messageId); 88 final InputStream in = cr.openInputStream(textUri); 89 final String underlyingTextString; 90 try { 91 underlyingTextString = IOUtils.toString(in); 92 } finally { 93 in.close(); 94 } 95 mTextParts.put(position, underlyingTextString); 96 } 97 } catch (final IOException e) { 98 LogUtils.v(LogUtils.TAG, e, "Did not find text body for message %d", messageId); 99 } 100 } 101 cursor.moveToPosition(-1); 102 } 103 104 @Override 105 public String getString(final int columnIndex) { 106 if (columnIndex == mHtmlColumnIndex) { 107 return mHtmlParts.get(getPosition()); 108 } else if (columnIndex == mTextColumnIndex) { 109 return mTextParts.get(getPosition()); 110 } 111 return super.getString(columnIndex); 112 } 113 114 @Override 115 public int getType(int columnIndex) { 116 if (columnIndex == mHtmlColumnIndex || columnIndex == mTextColumnIndex) { 117 // Need to force this, otherwise we might fall through to some other get*() method 118 // instead of getString() if the underlying cursor has other ideas about this content 119 return FIELD_TYPE_STRING; 120 } else { 121 return super.getType(columnIndex); 122 } 123 } 124 } 125