/** * * This file contains code from the IOCipher Camera Library "CipherCam". * * For more information about IOCipher, see https://guardianproject.info/code/iocipher * and this sample library: https://github.com/n8fr8/IOCipherCameraExample * * IOCipher Camera Sample is distributed under this license (aka the 3-clause BSD license) * * @author n8fr8 * */ package info.guardianproject.iocipher.camera; import info.guardianproject.cacheword.CacheWordHandler; import info.guardianproject.cacheword.ICacheWordSubscriber; import info.guardianproject.iocipher.File; import info.guardianproject.iocipher.FileInputStream; import info.guardianproject.iocipher.FileOutputStream; import info.guardianproject.iocipher.camera.io.IOCipherContentProvider; import info.guardianproject.iocipher.camera.io.PgpHelper; import info.guardianproject.iocipher.camera.viewer.ImageViewerActivity; import info.guardianproject.iocipher.camera.viewer.MjpegViewerActivity; import java.io.BufferedOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.Security; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.ActivityNotFoundException; import android.content.ContentResolver; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.provider.MediaStore; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.webkit.MimeTypeMap; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ArrayAdapter; import android.widget.GridView; import android.widget.ImageView; import android.widget.Toast; import org.spongycastle.openpgp.PGPPublicKey; public class GalleryActivity extends Activity implements ICacheWordSubscriber { private final static String TAG = "FileBrowser"; private List<String> item = null; private List<String> path = null; private String[] items; private java.io.File dbFile; private String root = "/"; private GridView gridview; private HashMap<String,Bitmap> mBitCache = new HashMap<String,Bitmap>(); private HashMap<String,BitmapWorkerThread> mBitLoaders = new HashMap<String,BitmapWorkerThread>(); private CacheWordHandler mCacheWord; private final static int REQUEST_TAKE_PICTURE = 1000; private final static int REQUEST_TAKE_VIDEO = 1001; private final static String ACTION_SECURE_STILL_IMAGE_CAMERA = "info.guardianproject.action.SECURE_STILL_IMAGE_CAMERA"; private final static String ACTION_SECURE_SECURE_VIDEO_CAMERA = "info.guardianproject.action.SECURE_VIDEO_CAMERA"; private Handler h = new Handler();//for UI event handling private boolean mUseBuiltInLockScreen = true; private boolean isExternalLaunch = false; static { Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1); Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider()); } /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //prevent screenshots getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); setContentView(R.layout.activity_gallery); gridview = (GridView) findViewById(R.id.gridview); mCacheWord = new CacheWordHandler(this, this); mCacheWord.connectToService(); Intent intent = getIntent(); String action = intent.getAction(); String type = intent.getType(); if (Intent.ACTION_SEND.equals(action) && type != null) { if (intent.hasExtra(Intent.EXTRA_STREAM)) { Log.i(TAG, "save extra stream URI"); handleSendUri((Uri) intent.getExtras().get(Intent.EXTRA_STREAM)); } else { Log.i(TAG, "save data"); handleSendUri(intent.getData()); } } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(action) || ACTION_SECURE_STILL_IMAGE_CAMERA.equals(action) ) { //REQUEST_TAKE_PICTURE Intent intentCapture = new Intent(this,StillCameraActivity.class); intentCapture.setAction(MediaStore.ACTION_IMAGE_CAPTURE); intentCapture.putExtra("basepath", "/"); intentCapture.putExtra("selfie", false); startActivityForResult(intentCapture, REQUEST_TAKE_PICTURE); isExternalLaunch = true; } else if (MediaStore.ACTION_VIDEO_CAPTURE.equals(action) || ACTION_SECURE_SECURE_VIDEO_CAMERA.equals(action) ) { //REQUEST_TAKE_VIDEO Intent intentCapture = new Intent(this,VideoCameraActivity.class); intentCapture.setAction(MediaStore.ACTION_VIDEO_CAPTURE); intentCapture.putExtra("basepath", "/"); intentCapture.putExtra("selfie", false); startActivityForResult(intentCapture, REQUEST_TAKE_VIDEO); isExternalLaunch = true; } setIntent(null); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); } protected void onResume() { super.onResume(); if (!StorageManager.isStorageMounted()) { goToLockScreen (); } else { mCacheWord.reattach(); } } @Override public void onCacheWordLocked() { if (StorageManager.isStorageMounted()) { //if storage is mounted, then we should lock it boolean unmounted = StorageManager.unmountStorage(); } goToLockScreen (); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_TAKE_PICTURE) { if (resultCode == RESULT_OK) { String[] ioCipherFile = data.getExtras().getStringArray(MediaStore.EXTRA_OUTPUT); if (ioCipherFile != null && ioCipherFile.length > 0) { String sharePath = IOCipherContentProvider.addShare(ioCipherFile[0], IOCipherContentProvider.DEFAULT_AUTHORITY); Uri uri = Uri.parse(sharePath); String mimeType = "image/*"; data.setDataAndType(uri, mimeType); data.putExtra(Intent.EXTRA_STREAM, uri); data.putExtra(MediaStore.EXTRA_OUTPUT, ioCipherFile); setResult(resultCode,data); } } } else if (requestCode == REQUEST_TAKE_VIDEO) { if (resultCode == RESULT_OK) { String[] ioCipherFile = data.getExtras().getStringArray(MediaStore.EXTRA_OUTPUT); if (ioCipherFile != null && ioCipherFile.length > 0) { String sharePath = IOCipherContentProvider.addShare(ioCipherFile[0], IOCipherContentProvider.DEFAULT_AUTHORITY); Uri uri = Uri.parse(sharePath); String mimeType = "video/*"; data.setDataAndType(uri, mimeType); data.putExtra(Intent.EXTRA_STREAM, uri); data.putExtra(MediaStore.EXTRA_OUTPUT, ioCipherFile); setResult(resultCode,data); } } } if (isExternalLaunch) finish(); else getFileList(root); } @Override public void onCacheWordOpened() { mCacheWord.setTimeout(0); //great! getFileList(root); } @Override public void onCacheWordUninitialized() { goToLockScreen(); } private void goToLockScreen () { try { mCacheWord.disconnectFromService(); } catch (IllegalArgumentException iae) { Log.d(TAG,"error disconnecting from cacheword service",iae); } if (mUseBuiltInLockScreen && (!isExternalLaunch)) { Intent intent = new Intent(this,LockScreenActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); finish(); } } @Override protected void onPause() { super.onPause(); mCacheWord.detach(); } protected void onDestroy() { super.onDestroy(); mCacheWord.disconnectFromService(); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { Intent intent = null; int itemId = item.getItemId(); if (itemId == R.id.menu_camera) { intent = new Intent(this,StillCameraActivity.class); intent.putExtra("basepath", "/"); intent.putExtra("selfie", false); startActivityForResult(intent, 1); return true; } else if (itemId == R.id.menu_video) { intent = new Intent(this,VideoCameraActivity.class); intent.putExtra("basepath", "/"); intent.putExtra("selfie", false); startActivityForResult(intent, 1); return true; } else if (itemId == R.id.menu_lock) { if (StorageManager.isStorageMounted()) { //if storage is mounted, then we should lock it boolean unmounted = StorageManager.unmountStorage(); if (!unmounted) { Toast.makeText(this, "Storage is busy... cannot lock yet.",Toast.LENGTH_LONG).show(); } else { mCacheWord.lock(); } } return true; } return false; } private void handleSendUri(Uri dataUri) { try { ContentResolver cr = getContentResolver(); InputStream in = cr.openInputStream(dataUri); Log.i(TAG, "incoming URI: " + dataUri.toString()); String fileName = dataUri.getLastPathSegment(); File f = new File("/" + fileName); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(f)); readBytesAndClose(in, out); Log.v(TAG, f.getAbsolutePath() + " size: " + String.valueOf(f.length())); } catch (Exception e) { Log.e(TAG, e.getMessage(), e); } } private void readBytesAndClose(InputStream in, OutputStream out) throws IOException { try { int block = 8 * 1024; // IOCipher works best with 8k blocks byte[] buff = new byte[block]; while (true) { int len = in.read(buff, 0, block); if (len < 0) { break; } out.write(buff, 0, len); } } finally { in.close(); out.flush(); out.close(); } } // To make listview for the list of file public void getFileList(String dirPath) { item = new ArrayList<String>(); path = new ArrayList<String>(); File file = new File(dirPath); File[] files = file.listFiles(); if (!dirPath.equals(root)) { item.add(root); path.add(root);// to get back to main list item.add(".."); path.add(file.getParent()); // back one level } for (int i = files.length-1; i >= 0; i--) { File fileItem = files[i]; path.add(fileItem.getPath()); if (fileItem.isDirectory()) { // input name directory to array list item.add("[" + fileItem.getName() + "]"); } else { // input name file to array list item.add(fileItem.getName()); } } // declare array with specific number of items items = new String[item.size()]; // send data arraylist(item) to array(items) item.toArray(items); gridview.setAdapter(new IconicList()); gridview.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View v, int position, long id) { File file = new File(path.get(position)); if (file.isDirectory()) { if (file.canRead()) { getFileList(path.get(position)); } else { //show error } } else { showItem (file); } } }); gridview.setOnItemLongClickListener(new OnItemLongClickListener () { @Override public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int position, long arg3) { File file = new File(path.get(position)); if (file.isDirectory()) { if (file.canRead()) { getFileList(path.get(position)); return true; } else { //show error } } else { showItemDialog (file); return true; } return false; } }); } private void showItemDialog (final File file) { new AlertDialog.Builder(GalleryActivity.this) .setTitle("[" + file.getName() + "]") .setNegativeButton("Delete", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { file.delete(); getFileList(root); } }) .setNeutralButton("Time Campsule...", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { new PGPEncryptTask(GalleryActivity.this).execute(file); } }).show(); /* .setPositiveButton("Share...", new DialogInterface.OnClickListener() { // @Override public void onClick(DialogInterface dialog, int which) { //Log.i(TAG,"open URL: " + Uri.parse(IOCipherContentProvider.FILES_URI + file.getName())); String sharePath = IOCipherContentProvider.addShare(file.getName(), IOCipherContentProvider.DEFAULT_AUTHORITY); Uri uri = Uri.parse(sharePath); //java.io.File exportFile = exportToDisk(file); //Uri uriExport = Uri.fromFile(exportFile); Intent intent = new Intent(Intent.ACTION_SEND); String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString()); String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension); if (fileExtension.equals("mp4") || fileExtension.equals("mkv") || fileExtension.equals("mov")) mimeType = "video/*"; if (mimeType == null) mimeType = "application/octet-stream"; intent.setDataAndType(uri, mimeType); intent.putExtra(Intent.EXTRA_STREAM, uri); intent.putExtra(Intent.EXTRA_SUBJECT, file.getName()); intent.putExtra(Intent.EXTRA_TITLE, file.getName()); try { startActivity(Intent.createChooser(intent, "Share this!")); } catch (ActivityNotFoundException e) { Log.e(TAG, "No relevant Activity found", e); } } }).show();*/ } private void shareExternalFile (java.io.File fileExtern) { Uri uri = Uri.fromFile(fileExtern); Intent intent = new Intent(Intent.ACTION_SEND); String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString()); String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension); if (fileExtension.equals("mp4") || fileExtension.equals("mkv") || fileExtension.equals("mov")) mimeType = "video/*"; if (mimeType == null) mimeType = "application/octet-stream"; intent.setDataAndType(uri, mimeType); intent.putExtra(Intent.EXTRA_STREAM, uri); intent.putExtra(Intent.EXTRA_SUBJECT, fileExtern.getName()); intent.putExtra(Intent.EXTRA_TITLE, fileExtern.getName()); try { startActivity(Intent.createChooser(intent, "Share this!")); } catch (ActivityNotFoundException e) { Log.e(TAG, "No relevant Activity found", e); } } private void showItem (File file) { try { String fileExtension = MimeTypeMap.getFileExtensionFromUrl(file.getAbsolutePath()); String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension); if (fileExtension.equals("ts")) mimeType = "application/mpeg*"; if (mimeType == null) mimeType = "application/octet-stream"; if (mimeType.startsWith("image")) { Intent intent = new Intent(GalleryActivity.this,ImageViewerActivity.class); intent.setType(mimeType); intent.putExtra("vfs", file.getAbsolutePath()); startActivity(intent); } else if (fileExtension.equals("mp4") || mimeType.startsWith("video")) { Intent intent = new Intent(GalleryActivity.this,MjpegViewerActivity.class); intent.setType(mimeType); intent.putExtra("video", file.getAbsolutePath()); startActivity(intent); } else { String sharePath = IOCipherContentProvider.addShare(file.getName(), IOCipherContentProvider.DEFAULT_AUTHORITY); Uri uri = Uri.parse(sharePath); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(uri, mimeType); startActivity(intent); } } catch (ActivityNotFoundException e) { Log.e(TAG, "No relevant Activity found", e); } } static class ViewHolder { ImageView icon; } class IconicList extends ArrayAdapter<Object> { public IconicList() { super(GalleryActivity.this, R.layout.gallery_gridsq, items); } public View getView(int position, View convertView, ViewGroup parent) { LayoutInflater inflater = getLayoutInflater(); ViewHolder holder = null; if (convertView == null) convertView = inflater.inflate(R.layout.gallery_gridsq, null); else holder = (ViewHolder)convertView.getTag(); if (holder == null) { holder = new ViewHolder(); holder.icon = (ImageView) convertView.findViewById(R.id.icon); holder.icon.setImageResource(R.drawable.text); } File f = new File(path.get(position)); // get the file according the // position String mimeType = null; String[] tokens = f.getName().split("\\.(?=[^\\.]+$)"); if (tokens.length > 1) mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(f.getName().split("\\.")[1]); if (mimeType == null) mimeType = "application/octet-stream"; if (f.isDirectory()) { holder.icon.setImageResource(R.drawable.folder); } else if (mimeType.startsWith("image")){ try { Bitmap b = getPreview(f); if (b != null) holder.icon.setImageBitmap(b); else holder.icon.setImageResource(R.drawable.jpeg);//placeholder while its loading } catch (Exception e) { Log.d(TAG,"error showing thumbnail",e); holder.icon.setImageResource(R.drawable.text); } } else if (mimeType.startsWith("audio")||f.getName().endsWith(".pcm")||f.getName().endsWith(".aac")) { holder.icon.setImageResource(R.drawable.audio); } else if (mimeType.startsWith("video")||f.getName().endsWith(".mp4")||f.getName().endsWith(".mov")) { holder.icon.setImageResource(R.drawable.mp4); } else { holder.icon.setImageResource(R.drawable.text); } return (convertView); } } private Bitmap getPreview(File fileImage) throws FileNotFoundException { Bitmap b = null; b = mBitCache.get(fileImage.getAbsolutePath()); if (b == null && mBitLoaders.get(fileImage.getAbsolutePath())==null) { BitmapWorkerThread bwt = new BitmapWorkerThread(fileImage); mBitLoaders.put(fileImage.getAbsolutePath(),bwt); bwt.start(); } return b; } class BitmapWorkerThread extends Thread { private File fileImage; public BitmapWorkerThread (File fileImage) { this.fileImage = fileImage; } public void run () { BitmapFactory.Options bounds = new BitmapFactory.Options(); bounds.inSampleSize = 8; Bitmap b; try { FileInputStream fis = new FileInputStream(fileImage); b = BitmapFactory.decodeStream(fis, null, bounds); fis.close(); mBitCache.put(fileImage.getAbsolutePath(), b); mBitLoaders.remove(fileImage.getAbsolutePath()); h.post(new Runnable() { public void run () { ((IconicList)gridview.getAdapter()).notifyDataSetChanged(); } }); //VirtualFileSystem.get().detachThread(); } catch (Exception e) { Log.e(TAG,"error decoding bitmap preview",e); } } } /* class BitmapWorkerTask extends AsyncTask<File, Void, Bitmap> { // Decode image in background. @Override protected Bitmap doInBackground(File... fileImage) { BitmapFactory.Options bounds = new BitmapFactory.Options(); bounds.inSampleSize = 8; Bitmap b; try { FileInputStream fis = new FileInputStream(fileImage[0]); b = BitmapFactory.decodeStream(fis, null, bounds); fis.close(); mBitCache.put(fileImage[0].getAbsolutePath(), b); return b; } catch (Exception e) { Log.e(TAG,"error decoding bitmap preview",e); } return null; } // Once complete, see if ImageView is still around and set bitmap. @Override protected void onPostExecute(Bitmap bitmap) { ((IconicList)gridview.getAdapter()).notifyDataSetChanged(); } }*/ public void setUseBuiltInLockScreen(boolean useBuiltInLockScreen) { this.mUseBuiltInLockScreen = useBuiltInLockScreen; } class PGPEncryptTask extends AsyncTask<File, Integer, java.io.File> { private Activity activity; private ProgressDialog dialog; public PGPEncryptTask(Activity activity){ this.activity = activity; this.dialog = new ProgressDialog(activity); this.dialog.setTitle("Exporting to Time Capsule"); this.dialog.setMessage("please wait..."); if(!this.dialog.isShowing()){ this.dialog.show(); } } @Override protected java.io.File doInBackground(File... params) { try { String fileName = "/sdcard/" + new Date().getTime() + ".pgp.asc"; java.io.File fileTimeCap = new java.io.File(fileName); java.io.FileOutputStream osTimeCap = new java.io.FileOutputStream(fileTimeCap); boolean asciiArmor = true; boolean integrityCheck = true; PgpHelper pgpHelper = PgpHelper.getInstance(); PGPPublicKey encKey = pgpHelper.readPublicKey(new java.io.FileInputStream(new java.io.File("/sdcard/jack.asc"))); pgpHelper.encryptStream(osTimeCap, new FileInputStream(params[0]), params[0].getName(), params[0].length(), new Date(), encKey, asciiArmor, integrityCheck); params[0].delete(); return fileTimeCap; } catch (Exception ioe) { Log.e("PGPExport","unable to export",ioe); Toast.makeText(activity,"Unable to export to PGP: " + ioe.getMessage(),Toast.LENGTH_LONG).show(); } return null; } @Override protected void onPostExecute(java.io.File fileTimeCap){ this.dialog.dismiss(); getFileList(root); if (fileTimeCap != null) shareExternalFile(fileTimeCap); } } }