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