Home | History | Annotate | Download | only in provider
      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