package info.guardianproject.pixelknot; import android.annotation.SuppressLint; import android.app.Activity; import android.content.res.Resources; import android.graphics.Bitmap; import android.os.FileObserver; import android.text.TextUtils; import android.util.Log; import com.squareup.picasso.Picasso; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Map; import info.guardianproject.f5android.plugins.PluginNotificationListener; import info.guardianproject.f5android.plugins.f5.james.Jpeg; import info.guardianproject.f5android.plugins.f5.james.JpegEncoder; import info.guardianproject.pixelknot.crypto.Aes; public class StegoEncryptionJob extends StegoJob { private static final boolean LOGGING = false; private static final String LOGTAG = "StegoEncryptionJob"; public interface OnProgressListener { void onProgressUpdate(StegoEncryptionJob job, int percent); } private final DummyListenerActivity mActivity; private Bitmap mBitmap; private final String mImageName; private String mMessage; private final String mPassword; private File mInputFile; private File mOutputFile; private FileObserver mOutputFileObserver; private boolean mHasBeenShared; private int mOutputFileHandleCount; private boolean mDeleteOutputFileOnClose; private OnProgressListener mOnProgressListener; public StegoEncryptionJob(final IStegoThreadHandler threadHandler, File imageFile, String imageName, String message, String password) { super(threadHandler); mInputFile = App.getInstance().getFileManager().moveInputFileToJob(imageFile, getId()); mImageName = imageName; mMessage = message; mPassword = password; mOutputFileHandleCount = 0; mActivity = new DummyListenerActivity(); addProcess(new Runnable() { @SuppressLint("LongLogTag") @Override public void run() { try { mBitmap = Picasso.with(threadHandler.getContext()).load(mInputFile) .resize(Constants.MAX_IMAGE_PIXEL_SIZE, Constants.MAX_IMAGE_PIXEL_SIZE) .onlyScaleDown() .centerInside() .get(); } catch (Exception e) { Log.e(Jpeg.LOG, e.toString()); e.printStackTrace(); abortJob(); } onProgressTick(); } }, 1); if(hasPassword()) { addProcess(new Runnable() { @Override public void run() { Map.Entry<String, String> pack = Aes.EncryptWithPassword(getPassword(), mMessage, getPasswordSalt()).entrySet().iterator().next(); mMessage = Constants.PASSWORD_SENTINEL.concat(new String(pack.getKey())).concat(pack.getValue()); onProgressTick(); } }, 1); } addProcess(new Runnable() { @SuppressLint("LongLogTag") @Override public void run() { boolean success = false; try { createOutputFile(); FileOutputStream fos = new FileOutputStream(mOutputFile); JpegEncoder jpg = new JpegEncoder(mActivity /*mThreadHandler.getActivity()*/, mBitmap, Constants.OUTPUT_IMAGE_QUALITY, fos, getF5Seed(), getThread()); success = jpg.Compress(new ByteArrayInputStream(mMessage.getBytes())); fos.close(); } catch (Exception e) { Log.e(Jpeg.LOG, e.toString()); e.printStackTrace(); abortJob(); } setProcessingStatus(success ? ProcessingStatus.EMBEDDED_SUCCESSFULLY : ProcessingStatus.ERROR); } }, Constants.Steps.EMBED); /* StegoProcessThread debug = new StegoProcessThread(Jpeg.LOG) { @SuppressLint("LongLogTag") @Override public void run() { super.run(); boolean success = false; try { Thread.sleep(2000); createOutputFile(); InputStream is = mThreadHandler.getActivity().getResources().getAssets().open("test.jpg"); FileOutputStream fos = new FileOutputStream(mOutputFile); int cb = 0; byte[] buf = new byte[1024]; while ((cb = is.read(buf)) >= 0) { fos.write(buf, 0, cb); } fos.close(); is.close(); } catch (Exception e) { Log.e(Jpeg.LOG, e.toString()); e.printStackTrace(); ((PluginNotificationListener) mThreadHandler.getActivity()).onFailure(); } mThreadHandler.onProcessDone(this); mThreadHandler.onJobDone(StegoEncryptionJob.this, success); } }; mThreadHandler.addProcess(debug); */ Run(); } public void setOnProgressListener(OnProgressListener listener) { mOnProgressListener = listener; } private boolean hasPassword() { return !TextUtils.isEmpty(mPassword); } private String extractPasswordSalt(String from_password) { return from_password.substring((int) (from_password.length()/3), (int) ((from_password.length()/3)*2)); } private String extractF5Seed(String from_password) { return from_password.substring((int) ((from_password.length()/3)*2)); } private String extractPassword(String from_password) { return from_password.substring(0, (int) (from_password.length()/3)); } private String getPassword() { if(!hasPassword()) { return null; } return extractPassword(mPassword); } private byte[] getPasswordSalt() { if(!hasPassword()) { return Constants.DEFAULT_PASSWORD_SALT; } return extractPasswordSalt(mPassword).getBytes(); } private byte[] getF5Seed() { if(!hasPassword()) { return Constants.DEFAULT_F5_SEED; } return extractF5Seed(mPassword).getBytes(); } public File getBitmapFile() { return mInputFile; } public long getOutputLength() { return mOutputFile.length(); } public File getOutputFile() { return mOutputFile; } public String getOutputImageName() { return mImageName; } public boolean getHasBeenShared() { return mHasBeenShared; } public void setHasBeenShared(boolean hasBeenShared) { mHasBeenShared = hasBeenShared; } private void createOutputFile() throws IOException { mOutputFile = App.getInstance().getFileManager().createFileForJob(getId()); mOutputFileObserver = new FileObserver(mOutputFile.getAbsolutePath(), FileObserver.OPEN | FileObserver.CLOSE_WRITE | FileObserver.CLOSE_NOWRITE) { @Override public void onEvent(int event, String path) { synchronized (StegoEncryptionJob.this) { if (event == FileObserver.OPEN) { mHasBeenShared = true; mOutputFileHandleCount++; if (LOGGING) Log.d(LOGTAG, "file opened, handle count " + mOutputFileHandleCount + " for file " + mOutputFile.getPath()); } else if (event == FileObserver.CLOSE_WRITE || event == FileObserver.CLOSE_NOWRITE) { mOutputFileHandleCount--; if (LOGGING) Log.d(LOGTAG, "file closed, handle count " + mOutputFileHandleCount + " for file " + mOutputFile.getPath()); if (mOutputFileHandleCount == 0 && mDeleteOutputFileOnClose) { if (LOGGING) Log.d(LOGTAG, "file handle count is 0 and file marked for deletion, deleting file " + mOutputFile.getPath()); mDeleteOutputFileOnClose = false; mOutputFileObserver.stopWatching(); mOutputFile.delete(); } } } } }; mOutputFileObserver.startWatching(); } @Override public void cleanup() { super.cleanup(); try { synchronized (this) { if (mInputFile != null) { if (mInputFile.exists()) mInputFile.delete(); mInputFile = null; } if (mOutputFile != null) { if (mOutputFileHandleCount == 0) { if (LOGGING) Log.d(LOGTAG, "cleanup - file not open, deleting it now"); mOutputFileObserver.stopWatching(); mOutputFile.delete(); } else { if (LOGGING) Log.d(LOGTAG, "cleanup - file is open, mark for deletion"); mDeleteOutputFileOnClose = true; } } } } catch (Exception ignored) {} } @Override void setProcessingStatus(ProcessingStatus processingStatus) { super.setProcessingStatus(processingStatus); if (mOnProgressListener != null) { mOnProgressListener.onProgressUpdate(StegoEncryptionJob.this, getProgressPercent()); } } @Override void onProgressTick() { super.onProgressTick(); if (mOnProgressListener != null) { mOnProgressListener.onProgressUpdate(StegoEncryptionJob.this, getProgressPercent()); } } private class DummyListenerActivity extends Activity implements PluginNotificationListener { @Override public Resources getResources() { return mThreadHandler.getContext().getResources(); } @Override public void onUpdate(String with_message) { onProgressTick(); } @Override public void onFailure() { } } }