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