/*
Copyright (C) 2013-2015, Silent Circle, LLC. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Any redistribution, use, or modification is done solely for personal
benefit and not for any commercial purpose or for monetary gain
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name Silent Circle nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL SILENT CIRCLE, LLC BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.silentcircle.silenttext.activity;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import com.silentcircle.silenttext.R;
import com.silentcircle.silenttext.application.SilentTextApplication;
import com.silentcircle.silenttext.client.model.DownloadManagerEntry;
import com.silentcircle.silenttext.fragment.FileViewerFragment;
import com.silentcircle.silenttext.fragment.ImageViewerFragment;
import com.silentcircle.silenttext.fragment.MediaPlayerFragmentICS;
import com.silentcircle.silenttext.fragment.TextViewerFragment;
import com.silentcircle.silenttext.listener.ConfirmDialogNoRepeat;
import com.silentcircle.silenttext.listener.OnConfirmNoRepeatListener;
import com.silentcircle.silenttext.listener.OnProgressUpdateListener;
import com.silentcircle.silenttext.model.UserPreferences;
import com.silentcircle.silenttext.util.AsyncUtils;
import com.silentcircle.silenttext.util.AttachmentUtils;
import com.silentcircle.silenttext.util.Constants;
import com.silentcircle.silenttext.util.IOUtils;
import com.silentcircle.silenttext.util.MIME;
import com.silentcircle.silenttext.view.UploadView;
public class FileViewerActivity extends SilentActivity implements OnProgressUpdateListener, FileViewerFragment.Callback {
class ExportFileTask extends AsyncTask<MenuItem, Void, MenuItem> {
@Override
protected MenuItem doInBackground( MenuItem... items ) {
copyToExternalStorage( file );
return items[0];
}
@Override
protected void onPostExecute( MenuItem item ) {
super.onPostExecute( item );
item.setEnabled( true );
getSilentTextApplication().getDownloadManagerRepository().save( new DownloadManagerEntry( AttachmentUtils.addToDownloadManager( getApplicationContext(), file.getName(), file.getName(), true, mimeType, AttachmentUtils.getExternalStorageFile( getActivity(), file ).getPath(), fileSize, true ), Uri.fromFile( AttachmentUtils.getExternalStorageFile( getActivity(), file ) ) ) );
export( file, mimeType, false );
}
}
protected class ProcessOptionsOnConfirmListener implements OnConfirmNoRepeatListener, DialogInterface.OnClickListener {
private final MenuItem item;
ProcessOptionsOnConfirmListener( MenuItem item ) {
this.item = item;
}
@Override
public void onClick( DialogInterface arg0, int arg1 ) {
// export cancelled
// do nothing
}
@Override
public void onConfirm( Context context, boolean shouldNotShowAgain ) {
UserPreferences preferences = getSilentTextApplication().getUserPreferences();
preferences.ignoreWarningDecryptExternalStore = shouldNotShowAgain;
// save prefs
SilentTextApplication.from( context ).saveUserPreferences( preferences );
// process the item as intended
processMenuItem( item );
}
}
class ShareFileTask extends AsyncTask<MenuItem, Void, MenuItem> {
@Override
protected MenuItem doInBackground( MenuItem... args ) {
share( file, mimeType );
return args[0];
}
}
class ViewExportedFileTask extends AsyncTask<MenuItem, Void, MenuItem> {
@Override
protected MenuItem doInBackground( MenuItem... args ) {
export( file, mimeType, true );
return args[0];
}
}
private static final int EXTERNAL_ACTIVITY_REQUEST = R.id.export & 0xFFFF;
private static Fragment createMediaPlayerFragment( Uri uri, String mimeType ) {
return MediaPlayerFragmentICS.create( uri, mimeType );
}
protected File file;
protected String mimeType;
protected boolean exporting;
protected long fileSize;
private Uri uri;
protected void askUserWhatToDoWithThisFile( Uri fileURI, String fileMimeType ) {
setContentFragment( FileViewerFragment.create( fileURI, fileMimeType ) );
getActionBar().show();
}
private void burn( File burnFile ) {
File externalFile = AttachmentUtils.getExternalStorageFile( this, burnFile );
if( externalFile != null && externalFile.exists() ) {
externalFile.delete();
DownloadManagerEntry entry = getSilentTextApplication().getDownloadManagerRepository().findByID( externalFile.getPath().toCharArray() );
if( entry != null ) {
AttachmentUtils.removeFromDownloadManager( getApplicationContext(), entry.getDownloadId() );
}
getSilentTextApplication().getDownloadManagerRepository().removeByID( externalFile.getPath().toCharArray() );
}
finish();
}
protected File copyToExternalStorage( File file ) {
return copyToExternalStorage( file, this );
}
protected File copyToExternalStorage( File file, OnProgressUpdateListener onProgressUpdate ) {
File externalFile = AttachmentUtils.getExternalStorageFile( this, file );
if( externalFile == null || externalFile.exists() ) {
return externalFile;
}
fileSize = AttachmentUtils.getFileSize( this, Uri.fromFile( file ) );
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream( file );
out = new FileOutputStream( externalFile, false );
exporting = true;
IOUtils.pipe( in, out, onProgressUpdate );
exporting = false;
setVisibleIf( false, R.id.export );
toast( R.string.saved_to, externalFile.getAbsolutePath() );
invalidateSupportOptionsMenu();
return externalFile;
} catch( IOException exception ) {
IOUtils.close( in, out );
externalFile.delete();
} finally {
IOUtils.close( in, out );
}
return null;
}
protected void export( File originalFile, String fileMimeType, boolean openAfterwards ) {
File externalFile = copyToExternalStorage( originalFile );
if( !openAfterwards ) {
return;
}
if( externalFile != null && externalFile.exists() ) {
Uri data = Uri.fromFile( externalFile );
Intent viewer = new Intent( Intent.ACTION_VIEW, data );
if( launch( R.string.view_with, externalFile.getName(), viewer, fileMimeType ) ) {
finish();
return;
}
}
toast( R.string.unable_to_display_file );
}
private boolean launch( int labelResourceID, String filename, Intent intent, String type ) {
intent.setDataAndType( intent.getData(), type );
if( startExternalActivity( intent, labelResourceID, filename ) ) {
return true;
}
return false;
}
@Override
protected void onActivityResult( int requestCode, int resultCode, Intent data ) {
if( requestCode == EXTERNAL_ACTIVITY_REQUEST ) {
if( resultCode == RESULT_OK ) {
finish();
return;
}
}
super.onActivityResult( requestCode, resultCode, data );
}
@Override
protected void onCreate( Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
setContentView( R.layout.fragment );
Intent intent = getIntent();
if( intent != null ) {
uri = intent.getData();
file = new File( uri.getPath() );
mimeType = intent.getType();
setTitle( file.getName() );
// check text/x-vcard before generic text/...
if( MIME.isContact( mimeType ) ) {
// setContentFragment( ContactViewerFragment.create( uri, mimeType ) );
// no longer using native contact viewer, use file viewer
setContentFragment( FileViewerFragment.create( uri, mimeType ) );
} else if( MIME.isText( mimeType ) ) {
setContentFragment( TextViewerFragment.create( uri, mimeType ) );
} else if( MIME.isImage( mimeType ) ) {
setContentFragment( ImageViewerFragment.create( uri, mimeType ) );
} else if( MIME.isVideo( mimeType ) ) {
setContentFragment( createMediaPlayerFragment( uri, mimeType ) );
} else if( MIME.isAudio( mimeType ) ) {
setContentFragment( createMediaPlayerFragment( uri, mimeType ) );
} else {
setContentFragment( FileViewerFragment.create( uri, mimeType ) );
}
}
}
@Override
public boolean onCreateOptionsMenu( Menu menu ) {
getMenuInflater().inflate( R.menu.player, menu );
boolean exported = AttachmentUtils.isExported( this, file );
menu.findItem( R.id.view ).setVisible( AttachmentUtils.resolves( getPackageManager(), Intent.ACTION_VIEW, uri, mimeType ) );
menu.findItem( R.id.share ).setVisible( AttachmentUtils.resolves( getPackageManager(), Intent.ACTION_SEND, uri, mimeType ) );
menu.findItem( R.id.save ).setVisible( !exported );
menu.findItem( R.id.burn ).setVisible( exported );
return super.onCreateOptionsMenu( menu );
}
@Override
public void onError( Uri fileURI, String fileMimeType ) {
askUserWhatToDoWithThisFile( fileURI, fileMimeType );
}
@Override
public boolean onOptionsItemSelected( MenuItem item ) {
switch( item.getItemId() ) {
case R.id.save:
case R.id.view:
case R.id.share:
if( !verifyUserOKToExport( item ) ) {
return true;
}
break;
}
return processMenuItem( item );
}
@Override
public void onProgressUpdate( long progress ) {
final int percent = (int) Math.ceil( 100.0 * progress / fileSize );
runOnUiThread( new Runnable() {
@Override
public void run() {
UploadView view = (UploadView) findViewById( R.id.export );
if( view == null ) {
return;
}
if( exporting && fileSize > 0 ) {
view.setProgress( R.string.progress_exporting, percent, null );
view.setVisibility( View.VISIBLE );
} else {
view.setVisibility( View.GONE );
}
}
} );
}
protected boolean processMenuItem( MenuItem item ) {
switch( item.getItemId() ) {
case R.id.save:
item.setEnabled( false );
tasks.add( AsyncUtils.execute( new ExportFileTask(), item ) );
return true;
case R.id.burn:
burn( file );
return true;
case R.id.view:
tasks.add( AsyncUtils.execute( new ViewExportedFileTask(), item ) );
return true;
case R.id.share:
Constants.mIsSharePhoto = true;
tasks.add( AsyncUtils.execute( new ShareFileTask(), item ) );
return true;
}
return super.onOptionsItemSelected( item );
}
protected void setContentFragment( Fragment fragment ) {
initializeErrorView();
FragmentManager manager = getFragmentManager();
Fragment existing = manager.findFragmentById( R.id.content );
if( existing == null ) {
manager.beginTransaction().add( R.id.content, fragment ).commit();
} else {
if( !existing.getClass().equals( fragment.getClass() ) ) {
manager.beginTransaction().replace( R.id.content, fragment ).commit();
}
}
}
protected void share( File shareFile, String shareMimeType ) {
File externalFile = copyToExternalStorage( shareFile );
if( externalFile == null || !externalFile.exists() ) {
return;
}
Uri data = Uri.fromFile( externalFile );
Intent sender = new Intent( Intent.ACTION_SEND );
sender.putExtra( Intent.EXTRA_STREAM, data );
launch( R.string.share_with, externalFile.getName(), sender, shareMimeType );
}
private boolean startExternalActivity( Intent intent, int chooserTitleID, Object... chooserTitleArgs ) {
if( intent.resolveActivity( getPackageManager() ) != null ) {
startActivityForResult( Intent.createChooser( intent, getString( chooserTitleID, chooserTitleArgs ) ), EXTERNAL_ACTIVITY_REQUEST );
return true;
}
return false;
}
protected boolean verifyUserOKToExport( MenuItem item ) {
UserPreferences preferences = getSilentTextApplication().getUserPreferences();
if( !preferences.ignoreWarningDecryptExternalStore ) {
ProcessOptionsOnConfirmListener listener = new ProcessOptionsOnConfirmListener( item );
ConfirmDialogNoRepeat alert = new ConfirmDialogNoRepeat( R.string.security_warning, R.string.verify_ok_media_to_be_exported, R.string.cancel, R.string._continue, this, listener, listener );
alert.show();
return false;
}
return true;
}
}