2011-01-06 11:56:20 -05:00
package com.fsck.k9.view ;
2014-10-20 19:39:16 -04:00
2011-04-23 23:56:34 -04:00
import java.io.File ;
import java.io.FileOutputStream ;
import java.io.IOException ;
import java.io.InputStream ;
import java.io.OutputStream ;
import org.apache.commons.io.IOUtils ;
2011-01-06 11:56:20 -05:00
import android.content.Context ;
import android.content.Intent ;
import android.graphics.Bitmap ;
import android.graphics.BitmapFactory ;
import android.net.Uri ;
2014-02-16 18:10:44 -05:00
import android.os.AsyncTask ;
2011-01-06 11:56:20 -05:00
import android.os.Environment ;
import android.util.AttributeSet ;
import android.util.Log ;
import android.view.View ;
2012-02-27 15:34:18 -05:00
import android.view.View.OnClickListener ;
import android.view.View.OnLongClickListener ;
2011-04-23 23:56:34 -04:00
import android.widget.Button ;
import android.widget.FrameLayout ;
import android.widget.ImageView ;
import android.widget.TextView ;
import android.widget.Toast ;
2011-01-06 11:56:20 -05:00
import com.fsck.k9.Account ;
import com.fsck.k9.K9 ;
import com.fsck.k9.R ;
import com.fsck.k9.controller.MessagingController ;
import com.fsck.k9.controller.MessagingListener ;
import com.fsck.k9.helper.MediaScannerNotifier ;
import com.fsck.k9.helper.SizeFormatter ;
import com.fsck.k9.helper.Utility ;
import com.fsck.k9.mail.Message ;
2012-02-27 15:00:44 -05:00
import com.fsck.k9.mail.MessagingException ;
2011-01-06 11:56:20 -05:00
import com.fsck.k9.mail.Part ;
2012-01-18 00:00:26 -05:00
import com.fsck.k9.mail.internet.MimeHeader ;
2011-01-06 11:56:20 -05:00
import com.fsck.k9.mail.internet.MimeUtility ;
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBodyPart ;
import com.fsck.k9.provider.AttachmentProvider ;
2014-10-20 19:39:16 -04:00
2012-02-27 15:34:18 -05:00
public class AttachmentView extends FrameLayout implements OnClickListener , OnLongClickListener {
2011-01-06 11:56:20 -05:00
private Context mContext ;
public Button viewButton ;
public Button downloadButton ;
public LocalAttachmentBodyPart part ;
private Message mMessage ;
private Account mAccount ;
private MessagingController mController ;
private MessagingListener mListener ;
public String name ;
public String contentType ;
public long size ;
2012-01-26 21:07:44 -05:00
2011-04-24 00:00:10 -04:00
private AttachmentFileDownloadCallback callback ;
2011-02-06 17:09:48 -05:00
public AttachmentView ( Context context , AttributeSet attrs , int defStyle ) {
2011-01-06 11:56:20 -05:00
super ( context , attrs , defStyle ) ;
mContext = context ;
}
2014-10-20 19:39:16 -04:00
2011-02-06 17:09:48 -05:00
public AttachmentView ( Context context , AttributeSet attrs ) {
2011-01-06 11:56:20 -05:00
super ( context , attrs ) ;
mContext = context ;
}
2014-10-20 19:39:16 -04:00
2011-02-06 17:09:48 -05:00
public AttachmentView ( Context context ) {
2011-01-06 11:56:20 -05:00
super ( context ) ;
mContext = context ;
}
2011-04-24 00:00:10 -04:00
public interface AttachmentFileDownloadCallback {
/ * *
* this method i called by the attachmentview when
* he wants to show a filebrowser
* the provider should show the filebrowser activity
* and save the reference to the attachment view for later .
* in his onActivityResult he can get the saved reference and
* call the saveFile method of AttachmentView
2014-10-20 19:39:16 -04:00
*
2011-04-24 00:00:10 -04:00
* @param view
* /
public void showFileBrowser ( AttachmentView caller ) ;
}
2012-01-18 00:00:26 -05:00
/ * *
* Populates this view with information about the attachment .
* < p >
* This method also decides which attachments are displayed when the " show attachments " button
* is pressed , and which attachments are only displayed after the " show more attachments "
* button was pressed . < br >
* Inline attachments with content ID and unnamed attachments fall into the second category .
* < / p >
*
* @param inputPart
* @param message
* @param account
* @param controller
* @param listener
*
* @return { @code true } for a regular attachment . { @code false } , otherwise .
2012-02-27 15:00:44 -05:00
*
* @throws MessagingException
2014-10-20 19:39:16 -04:00
* In case of an error
2012-01-18 00:00:26 -05:00
* /
public boolean populateFromPart ( Part inputPart , Message message , Account account ,
2012-02-27 15:00:44 -05:00
MessagingController controller , MessagingListener listener ) throws MessagingException {
2012-01-18 00:00:26 -05:00
boolean firstClassAttachment = true ;
2012-02-27 15:00:44 -05:00
part = ( LocalAttachmentBodyPart ) inputPart ;
2011-01-06 11:56:20 -05:00
2012-02-27 15:00:44 -05:00
contentType = MimeUtility . unfoldAndDecode ( part . getContentType ( ) ) ;
String contentDisposition = MimeUtility . unfoldAndDecode ( part . getDisposition ( ) ) ;
2011-01-06 11:56:20 -05:00
2012-02-27 15:00:44 -05:00
name = MimeUtility . getHeaderParameter ( contentType , " name " ) ;
if ( name = = null ) {
name = MimeUtility . getHeaderParameter ( contentDisposition , " filename " ) ;
}
2012-01-18 00:00:26 -05:00
2012-02-27 15:00:44 -05:00
if ( name = = null ) {
firstClassAttachment = false ;
String extension = MimeUtility . getExtensionByMimeType ( contentType ) ;
name = " noname " + ( ( extension ! = null ) ? " . " + extension : " " ) ;
}
2012-01-18 00:00:26 -05:00
2012-02-27 15:00:44 -05:00
// Inline parts with a content-id are almost certainly components of an HTML message
// not attachments. Only show them if the user pressed the button to show more
// attachments.
if ( contentDisposition ! = null & &
MimeUtility . getHeaderParameter ( contentDisposition , null ) . matches ( " ^(?i:inline) " )
& & part . getHeader ( MimeHeader . HEADER_CONTENT_ID ) ! = null ) {
firstClassAttachment = false ;
}
2011-01-06 11:56:20 -05:00
2012-02-27 15:00:44 -05:00
mAccount = account ;
mMessage = message ;
mController = controller ;
mListener = listener ;
2011-01-06 11:56:20 -05:00
2012-02-27 15:00:44 -05:00
String sizeParam = MimeUtility . getHeaderParameter ( contentDisposition , " size " ) ;
if ( sizeParam ! = null ) {
try {
size = Integer . parseInt ( sizeParam ) ;
} catch ( NumberFormatException e ) { /* ignore */ }
}
2012-02-27 14:29:22 -05:00
2012-02-27 15:00:44 -05:00
contentType = MimeUtility . getMimeTypeForViewing ( part . getMimeType ( ) , name ) ;
TextView attachmentName = ( TextView ) findViewById ( R . id . attachment_name ) ;
TextView attachmentInfo = ( TextView ) findViewById ( R . id . attachment_info ) ;
2014-02-16 18:10:44 -05:00
final ImageView attachmentIcon = ( ImageView ) findViewById ( R . id . attachment_icon ) ;
2012-02-27 15:00:44 -05:00
viewButton = ( Button ) findViewById ( R . id . view ) ;
downloadButton = ( Button ) findViewById ( R . id . download ) ;
if ( ( ! MimeUtility . mimeTypeMatches ( contentType , K9 . ACCEPTABLE_ATTACHMENT_VIEW_TYPES ) )
| | ( MimeUtility . mimeTypeMatches ( contentType , K9 . UNACCEPTABLE_ATTACHMENT_VIEW_TYPES ) ) ) {
viewButton . setVisibility ( View . GONE ) ;
}
if ( ( ! MimeUtility . mimeTypeMatches ( contentType , K9 . ACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES ) )
| | ( MimeUtility . mimeTypeMatches ( contentType , K9 . UNACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES ) ) ) {
downloadButton . setVisibility ( View . GONE ) ;
}
if ( size > K9 . MAX_ATTACHMENT_DOWNLOAD_SIZE ) {
viewButton . setVisibility ( View . GONE ) ;
downloadButton . setVisibility ( View . GONE ) ;
}
2012-02-27 15:34:18 -05:00
viewButton . setOnClickListener ( this ) ;
downloadButton . setOnClickListener ( this ) ;
downloadButton . setOnLongClickListener ( this ) ;
2012-02-27 15:00:44 -05:00
attachmentName . setText ( name ) ;
attachmentInfo . setText ( SizeFormatter . formatSize ( mContext , size ) ) ;
2014-02-16 18:10:44 -05:00
new AsyncTask < Void , Void , Bitmap > ( ) {
protected Bitmap doInBackground ( Void . . . asyncTaskArgs ) {
Bitmap previewIcon = getPreviewIcon ( ) ;
return previewIcon ;
}
protected void onPostExecute ( Bitmap previewIcon ) {
if ( previewIcon ! = null ) {
attachmentIcon . setImageBitmap ( previewIcon ) ;
} else {
attachmentIcon . setImageResource ( R . drawable . attached_image_placeholder ) ;
}
}
} . execute ( ) ;
2011-01-06 11:56:20 -05:00
2012-01-18 00:00:26 -05:00
return firstClassAttachment ;
2011-01-06 11:56:20 -05:00
}
2012-02-27 15:34:18 -05:00
@Override
public void onClick ( View view ) {
switch ( view . getId ( ) ) {
case R . id . view : {
onViewButtonClicked ( ) ;
break ;
}
case R . id . download : {
onSaveButtonClicked ( ) ;
break ;
}
}
}
@Override
public boolean onLongClick ( View view ) {
if ( view . getId ( ) = = R . id . download ) {
callback . showFileBrowser ( this ) ;
return true ;
}
return false ;
}
2011-02-06 17:09:48 -05:00
private Bitmap getPreviewIcon ( ) {
2013-08-02 12:37:00 -04:00
Bitmap icon = null ;
2011-02-06 17:09:48 -05:00
try {
2013-08-02 12:37:00 -04:00
InputStream input = mContext . getContentResolver ( ) . openInputStream (
2014-10-20 19:39:16 -04:00
AttachmentProvider . getAttachmentThumbnailUri ( mAccount ,
part . getAttachmentId ( ) ,
62 ,
62 ) ) ;
2013-08-02 12:37:00 -04:00
icon = BitmapFactory . decodeStream ( input ) ;
input . close ( ) ;
2011-02-06 17:09:48 -05:00
} catch ( Exception e ) {
2011-01-06 11:56:20 -05:00
/ *
* We don ' t care what happened , we just return null for the preview icon .
* /
}
2013-08-02 12:37:00 -04:00
return icon ;
2011-01-06 11:56:20 -05:00
}
2011-02-06 17:09:48 -05:00
private void onViewButtonClicked ( ) {
if ( mMessage ! = null ) {
2014-10-20 19:39:16 -04:00
mController . loadAttachment ( mAccount , mMessage , part , new Object [ ] { false , this } , mListener ) ;
2011-01-06 11:56:20 -05:00
}
}
2011-02-06 17:09:48 -05:00
private void onSaveButtonClicked ( ) {
2011-01-06 11:56:37 -05:00
saveFile ( ) ;
2011-01-06 11:56:20 -05:00
}
2011-04-24 00:00:10 -04:00
/ * *
* Writes the attachment onto the given path
2014-10-20 19:39:16 -04:00
*
* @param directory
* the base dir where the file should be saved .
2011-04-24 00:00:10 -04:00
* /
public void writeFile ( File directory ) {
2011-02-06 17:09:48 -05:00
try {
2012-04-01 15:14:43 -04:00
String filename = Utility . sanitizeFilename ( name ) ;
2012-01-26 16:25:46 -05:00
File file = Utility . createUniqueFile ( directory , filename ) ;
2011-02-06 17:09:48 -05:00
Uri uri = AttachmentProvider . getAttachmentUri ( mAccount , part . getAttachmentId ( ) ) ;
2011-01-06 11:56:20 -05:00
InputStream in = mContext . getContentResolver ( ) . openInputStream ( uri ) ;
OutputStream out = new FileOutputStream ( file ) ;
IOUtils . copy ( in , out ) ;
out . flush ( ) ;
out . close ( ) ;
in . close ( ) ;
2012-01-26 17:53:41 -05:00
attachmentSaved ( file . toString ( ) ) ;
2011-01-06 11:56:20 -05:00
new MediaScannerNotifier ( mContext , file ) ;
2011-02-06 17:09:48 -05:00
} catch ( IOException ioe ) {
2012-02-02 19:22:37 -05:00
if ( K9 . DEBUG ) {
Log . e ( K9 . LOG_TAG , " Error saving attachment " , ioe ) ;
}
2011-01-06 11:56:20 -05:00
attachmentNotSaved ( ) ;
}
}
2012-01-26 16:25:46 -05:00
2011-04-24 00:00:10 -04:00
/ * *
* saves the file to the defaultpath setting in the config , or if the config
* is not set = > to the Environment
* /
public void writeFile ( ) {
writeFile ( new File ( K9 . getAttachmentDefaultPath ( ) ) ) ;
}
2011-01-06 11:56:20 -05:00
2011-02-06 17:09:48 -05:00
public void saveFile ( ) {
2011-04-24 00:00:10 -04:00
//TODO: Can the user save attachments on the internal filesystem or sd card only?
2011-02-06 17:09:48 -05:00
if ( ! Environment . getExternalStorageState ( ) . equals ( Environment . MEDIA_MOUNTED ) ) {
2011-01-06 11:56:37 -05:00
/ *
* Abort early if there ' s no place to save the attachment . We don ' t want to spend
* the time downloading it and then abort .
* /
2014-10-20 19:50:41 -04:00
String message = mContext . getString ( R . string . message_view_status_attachment_not_saved ) ;
displayMessageToUser ( message ) ;
2011-01-06 11:56:37 -05:00
return ;
}
2011-02-06 17:09:48 -05:00
if ( mMessage ! = null ) {
mController . loadAttachment ( mAccount , mMessage , part , new Object [ ] { true , this } , mListener ) ;
2011-01-06 11:56:37 -05:00
}
}
2011-02-06 17:09:48 -05:00
public void showFile ( ) {
2011-03-24 18:36:59 -04:00
Uri uri = AttachmentProvider . getAttachmentUriForViewing ( mAccount , part . getAttachmentId ( ) ) ;
2011-01-06 11:56:20 -05:00
Intent intent = new Intent ( Intent . ACTION_VIEW ) ;
2011-11-06 17:00:25 -05:00
// We explicitly set the ContentType in addition to the URI because some attachment viewers (such as Polaris office 3.0.x) choke on documents without a mime type
intent . setDataAndType ( uri , contentType ) ;
intent . addFlags ( Intent . FLAG_GRANT_READ_URI_PERMISSION | Intent . FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET ) ;
2011-02-06 17:09:48 -05:00
try {
2011-01-06 11:56:20 -05:00
mContext . startActivity ( intent ) ;
2011-02-06 17:09:48 -05:00
} catch ( Exception e ) {
2011-01-06 11:56:20 -05:00
Log . e ( K9 . LOG_TAG , " Could not display attachment of type " + contentType , e ) ;
2014-10-20 19:50:41 -04:00
String message = mContext . getString ( R . string . message_view_no_viewer , contentType ) ;
displayMessageToUser ( message ) ;
2011-01-06 11:56:20 -05:00
}
}
2011-02-06 17:09:48 -05:00
public void attachmentSaved ( final String filename ) {
2014-10-20 19:50:41 -04:00
String message = mContext . getString ( R . string . message_view_status_attachment_saved , filename ) ;
displayMessageToUser ( message ) ;
2011-01-06 11:56:20 -05:00
}
2011-02-06 17:09:48 -05:00
public void attachmentNotSaved ( ) {
2014-10-20 19:50:41 -04:00
String message = mContext . getString ( R . string . message_view_status_attachment_not_saved ) ;
displayMessageToUser ( message ) ;
}
private void displayMessageToUser ( String message ) {
Toast . makeText ( mContext , message , Toast . LENGTH_LONG ) . show ( ) ;
2011-01-06 11:56:20 -05:00
}
2014-10-20 19:30:20 -04:00
2011-04-24 00:00:10 -04:00
public void setCallback ( AttachmentFileDownloadCallback callback ) {
this . callback = callback ;
}
2011-01-06 11:56:20 -05:00
}