2011-01-06 11:56:20 -05:00
package com.fsck.k9.view ;
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 ;
2011-04-23 23:56:34 -04:00
import android.content.pm.PackageManager ;
2011-01-06 11:56:20 -05:00
import android.graphics.Bitmap ;
import android.graphics.BitmapFactory ;
import android.net.Uri ;
import android.os.Environment ;
import android.util.AttributeSet ;
import android.util.Log ;
import android.view.View ;
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 ;
import com.fsck.k9.mail.Part ;
import com.fsck.k9.mail.internet.MimeUtility ;
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBodyPart ;
import com.fsck.k9.provider.AttachmentProvider ;
2011-02-06 17:09:48 -05:00
public class AttachmentView extends FrameLayout {
2012-01-26 21:21:20 -05:00
/ * *
2012-01-26 21:39:08 -05:00
* Regular expression that represents characters we won ' t allow in file names .
*
* < p >
* Allowed are :
* < ul >
* < li > word characters ( letters , digits , and underscores ) : { @code \ w } < / li >
* < li > spaces : { @code " " } < / li >
* < li > special characters : { @code ! } , { @code # } , { @code $ } , { @code % } , { @code & } , { @code ' } ,
* { @code ( } , { @code ) } , { @code - } , { @code @ } , { @code ^ } , { @code ` } , < code > & # 123 ; < / code > ,
* < code > & # 125 ; < / code > , { @code ~ } , { @code . } , { @code , } < / li >
* < / ul > < / p >
2012-01-26 21:21:20 -05:00
* /
2012-01-26 21:39:08 -05:00
private static final String INVALID_CHARACTERS = " [^ \\ w !#$%&'() \\ -@ \\ ^`{}~.,]+ " ;
2012-01-26 21:21:20 -05:00
/ * *
* Invalid characters in a file name are replaced by this character .
* /
private static final String REPLACEMENT_CHARACTER = " _ " ;
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 ;
public ImageView iconView ;
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 ;
}
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 ;
}
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
* @param view
* /
public void showFileBrowser ( AttachmentView caller ) ;
}
2011-02-06 17:09:48 -05:00
public boolean populateFromPart ( Part inputPart , Message message , Account account , MessagingController controller , MessagingListener listener ) {
try {
2011-01-06 11:56:20 -05:00
part = ( LocalAttachmentBodyPart ) inputPart ;
contentType = MimeUtility . unfoldAndDecode ( part . getContentType ( ) ) ;
String contentDisposition = MimeUtility . unfoldAndDecode ( part . getDisposition ( ) ) ;
name = MimeUtility . getHeaderParameter ( contentType , " name " ) ;
2011-02-06 17:09:48 -05:00
if ( name = = null ) {
2011-01-06 11:56:20 -05:00
name = MimeUtility . getHeaderParameter ( contentDisposition , " filename " ) ;
}
2011-02-06 17:09:48 -05:00
if ( name = = null ) {
2011-01-06 11:56:20 -05:00
return false ;
}
mAccount = account ;
mMessage = message ;
mController = controller ;
mListener = listener ;
size = Integer . parseInt ( MimeUtility . getHeaderParameter ( contentDisposition , " size " ) ) ;
2011-03-24 19:37:53 -04:00
contentType = MimeUtility . getMimeTypeForViewing ( part . getMimeType ( ) , name ) ;
2011-01-06 11:56:20 -05:00
TextView attachmentName = ( TextView ) findViewById ( R . id . attachment_name ) ;
TextView attachmentInfo = ( TextView ) findViewById ( R . id . attachment_info ) ;
ImageView attachmentIcon = ( ImageView ) findViewById ( R . id . attachment_icon ) ;
viewButton = ( Button ) findViewById ( R . id . view ) ;
downloadButton = ( Button ) findViewById ( R . id . download ) ;
if ( ( ! MimeUtility . mimeTypeMatches ( contentType , K9 . ACCEPTABLE_ATTACHMENT_VIEW_TYPES ) )
2011-02-06 17:09:48 -05:00
| | ( MimeUtility . mimeTypeMatches ( contentType , K9 . UNACCEPTABLE_ATTACHMENT_VIEW_TYPES ) ) ) {
2011-01-06 11:56:20 -05:00
viewButton . setVisibility ( View . GONE ) ;
2011-01-17 19:04:11 -05:00
}
2011-01-06 11:56:20 -05:00
if ( ( ! MimeUtility . mimeTypeMatches ( contentType , K9 . ACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES ) )
2011-02-06 17:09:48 -05:00
| | ( MimeUtility . mimeTypeMatches ( contentType , K9 . UNACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES ) ) ) {
2011-01-06 11:56:20 -05:00
downloadButton . setVisibility ( View . GONE ) ;
}
2011-02-06 17:09:48 -05:00
if ( size > K9 . MAX_ATTACHMENT_DOWNLOAD_SIZE ) {
2011-01-06 11:56:20 -05:00
viewButton . setVisibility ( View . GONE ) ;
downloadButton . setVisibility ( View . GONE ) ;
}
2011-02-06 17:09:48 -05:00
viewButton . setOnClickListener ( new OnClickListener ( ) {
2011-01-06 11:56:20 -05:00
@Override
2011-02-06 17:09:48 -05:00
public void onClick ( View v ) {
2011-01-06 11:56:20 -05:00
onViewButtonClicked ( ) ;
return ;
}
} ) ;
2011-02-06 17:09:48 -05:00
downloadButton . setOnClickListener ( new OnClickListener ( ) {
2011-01-06 11:56:20 -05:00
@Override
2011-02-06 17:09:48 -05:00
public void onClick ( View v ) {
2011-01-06 11:56:20 -05:00
onSaveButtonClicked ( ) ;
return ;
}
} ) ;
2011-04-24 00:00:10 -04:00
downloadButton . setOnLongClickListener ( new OnLongClickListener ( ) {
@Override
public boolean onLongClick ( View v ) {
callback . showFileBrowser ( AttachmentView . this ) ;
return true ;
}
} ) ;
2011-01-06 11:56:20 -05:00
attachmentName . setText ( name ) ;
attachmentInfo . setText ( SizeFormatter . formatSize ( mContext , size ) ) ;
Bitmap previewIcon = getPreviewIcon ( ) ;
2011-02-06 17:09:48 -05:00
if ( previewIcon ! = null ) {
2011-01-06 11:56:20 -05:00
attachmentIcon . setImageBitmap ( previewIcon ) ;
2011-02-06 17:09:48 -05:00
} else {
2011-01-06 11:56:20 -05:00
attachmentIcon . setImageResource ( R . drawable . attached_image_placeholder ) ;
}
}
2011-02-06 17:09:48 -05:00
catch ( Exception e ) {
Log . e ( K9 . LOG_TAG , " error " , e ) ;
2011-01-06 11:56:20 -05:00
}
return true ;
}
2011-02-06 17:09:48 -05:00
private Bitmap getPreviewIcon ( ) {
try {
2011-01-06 11:56:20 -05:00
return BitmapFactory . decodeStream (
mContext . getContentResolver ( ) . openInputStream (
AttachmentProvider . getAttachmentThumbnailUri ( mAccount ,
part . getAttachmentId ( ) ,
62 ,
62 ) ) ) ;
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 .
* /
return null ;
}
}
2011-02-06 17:09:48 -05:00
private void onViewButtonClicked ( ) {
if ( mMessage ! = null ) {
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
* @param directory : the base dir where the file should be saved .
* /
public void writeFile ( File directory ) {
2011-02-06 17:09:48 -05:00
try {
2012-01-26 21:21:20 -05:00
String filename = 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 ) {
2011-01-06 11:56:20 -05:00
attachmentNotSaved ( ) ;
}
}
2012-01-26 16:25:46 -05:00
/ * *
2012-01-26 21:21:20 -05:00
* Replace characters we don ' t allow in file names with a replacement character .
*
* @param filename
* The original file name .
2012-01-26 21:07:44 -05:00
*
2012-01-26 21:21:20 -05:00
* @return The sanitized file name containing only allowed characters .
2012-01-26 16:25:46 -05:00
* /
2012-01-26 21:21:20 -05:00
private String sanitizeFilename ( String filename ) {
return filename . replaceAll ( INVALID_CHARACTERS , REPLACEMENT_CHARACTER ) ;
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 .
* /
Toast . makeText ( mContext ,
mContext . getString ( R . string . message_view_status_attachment_not_saved ) ,
Toast . LENGTH_SHORT ) . show ( ) ;
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 ) ;
Toast toast = Toast . makeText ( mContext , mContext . getString ( R . string . message_view_no_viewer , contentType ) , Toast . LENGTH_LONG ) ;
toast . show ( ) ;
}
}
2011-01-17 05:41:48 -05:00
/ * *
* Check the { @link PackageManager } if the phone has an application
* installed to view this type of attachment .
* If not , { @link # viewButton } is disabled .
* This should be done in any place where
* attachment . viewButton . setEnabled ( enabled ) ; is called .
* This method is safe to be called from the UI - thread .
* /
2011-02-06 17:09:48 -05:00
public void checkViewable ( ) {
if ( viewButton . getVisibility ( ) = = View . GONE ) {
2011-01-17 19:04:11 -05:00
// nothing to do
return ;
}
2011-02-06 17:09:48 -05:00
if ( ! viewButton . isEnabled ( ) ) {
2011-01-17 19:04:11 -05:00
// nothing to do
return ;
}
2011-02-06 17:09:48 -05:00
try {
2011-03-24 18:36:59 -04:00
Uri uri = AttachmentProvider . getAttachmentUriForViewing ( mAccount , part . getAttachmentId ( ) ) ;
2011-01-17 19:04:11 -05:00
Intent intent = new Intent ( Intent . ACTION_VIEW ) ;
intent . setData ( uri ) ;
2011-11-06 17:00:25 -05:00
intent . addFlags ( Intent . FLAG_GRANT_READ_URI_PERMISSION | Intent . FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET ) ;
2011-02-06 17:09:48 -05:00
if ( intent . resolveActivity ( mContext . getPackageManager ( ) ) = = null ) {
2011-01-17 19:04:11 -05:00
viewButton . setEnabled ( false ) ;
}
// currently we do not cache re result.
2011-02-06 17:09:48 -05:00
} catch ( Exception e ) {
2011-01-17 19:04:11 -05:00
Log . e ( K9 . LOG_TAG , " Cannot resolve activity to determine if we shall show the 'view'-button for an attachment " , e ) ;
}
}
2011-01-17 05:41:48 -05:00
2011-02-06 17:09:48 -05:00
public void attachmentSaved ( final String filename ) {
2011-01-06 11:56:20 -05:00
Toast . makeText ( mContext , String . format (
mContext . getString ( R . string . message_view_status_attachment_saved ) , filename ) ,
Toast . LENGTH_LONG ) . show ( ) ;
}
2011-02-06 17:09:48 -05:00
public void attachmentNotSaved ( ) {
2011-01-06 11:56:20 -05:00
Toast . makeText ( mContext ,
mContext . getString ( R . string . message_view_status_attachment_not_saved ) ,
Toast . LENGTH_LONG ) . show ( ) ;
}
2011-04-24 00:00:10 -04:00
public AttachmentFileDownloadCallback getCallback ( ) {
return callback ;
}
public void setCallback ( AttachmentFileDownloadCallback callback ) {
this . callback = callback ;
}
2011-01-06 11:56:20 -05:00
}