package org.witness.informacam.intake; import info.guardianproject.iocipher.File; import java.io.IOException; import java.io.InputStream; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import org.apache.commons.lang3.ArrayUtils; import org.witness.informacam.json.JSONException; import org.witness.informacam.models.j3m.IDCIMEntry; import org.witness.informacam.models.j3m.IGenealogy; import org.witness.informacam.models.j3m.IIntakeData; import org.witness.informacam.models.media.IAsset; import org.witness.informacam.models.media.IImage; import org.witness.informacam.models.media.ILog; import org.witness.informacam.models.media.IMedia; import org.witness.informacam.models.media.IVideo; import org.witness.informacam.share.DropboxSyncManager; import org.witness.informacam.storage.IOUtility; import org.witness.informacam.utils.BackgroundProcessor; import org.witness.informacam.utils.BackgroundTask; import org.witness.informacam.utils.Constants.App.Storage; import org.witness.informacam.utils.Constants.Codes; import org.witness.informacam.utils.Constants.InformaCamEventListener; import org.witness.informacam.utils.Constants.Logger; import org.witness.informacam.utils.Constants.Models; import org.witness.informacam.utils.Constants.Models.IMedia.MimeType; import org.witness.informacam.utils.Constants.Models.IUser; import org.witness.informacam.utils.ImageUtility; import org.witness.informacam.utils.MediaHasher; import org.witness.informacam.utils.TimeUtility; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.ExifInterface; import android.media.MediaMetadataRetriever; import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.provider.MediaStore; public class EntryJob extends BackgroundTask { private static final long serialVersionUID = 3689090560752901928L; boolean isThumbnail; protected String parentId = null; protected String[] informaCache = null; protected long timeOffset = 0L; protected IDCIMEntry entry; protected final static String LOG = "************************** EntryJob **************************"; public EntryJob(BackgroundProcessor backgroundProcessor, IDCIMEntry entry, String parentId, String[] informaCache, long timeOffset) { super(backgroundProcessor); this.entry = entry; this.parentId = parentId; this.informaCache = informaCache; this.timeOffset = timeOffset; } @Override protected boolean onStart() { try { analyze(); if(entry != null) { if(!isThumbnail) { backgroundProcessor.numProcessing++; Bundle data = new Bundle(); data.putInt(Codes.Extras.MESSAGE_CODE, Codes.Messages.DCIM.ADD); data.putString(Codes.Extras.CONSOLIDATE_MEDIA, entry.originalHash); data.putInt(Codes.Extras.NUM_PROCESSING, backgroundProcessor.numProcessing); data.putInt(Codes.Extras.NUM_COMPLETED, backgroundProcessor.numCompleted); Message message = new Message(); message.setData(data); InformaCamEventListener mListener = informaCam.getEventListener(); if (mListener != null) { mListener.onUpdate(message); } IMedia media = new IMedia(); media.dcimEntry = entry; media.dcimEntry.timezone = TimeUtility.getTimezone(); media._id = media.generateId(entry.originalHash); if (informaCache != null && informaCache.length > 0) { media.associatedCaches = new ArrayList<String>(); media.associatedCaches.addAll(Arrays.asList(informaCache)); } media.genealogy = new IGenealogy(); media.genealogy.dateCreated = media.dcimEntry.timeCaptured; if(this.parentId != null) { ((ILog) informaCam.mediaManifest.getById(this.parentId)).attachedMedia.add(media._id); informaCam.mediaManifest.save(); } boolean isFinishedProcessing = false; if(entry.mediaType.equals(Models.IMedia.MimeType.IMAGE)) { IImage image = new IImage(media); if(image.analyze()) { image.intakeData = new IIntakeData(image.dcimEntry.timeCaptured, image.dcimEntry.timezone, timeOffset, ArrayUtils.toString(image.genealogy.hashes), image.dcimEntry.cameraComponent); //Logger.d(LOG, image.intakeData.asJson().toString()); informaCam.mediaManifest.addMediaItem(image); isFinishedProcessing = true; } } else if(entry.mediaType.startsWith(Models.IMedia.MimeType.VIDEO_BASE)) { IVideo video = new IVideo(media); if(video.analyze()) { video.intakeData = new IIntakeData(video.dcimEntry.timeCaptured, video.dcimEntry.timezone, timeOffset, ArrayUtils.toString(video.genealogy.hashes), video.dcimEntry.cameraComponent); //Logger.d(LOG, video.intakeData.asJson().toString()); informaCam.mediaManifest.addMediaItem(video); isFinishedProcessing = true; } } backgroundProcessor.numCompleted++; if(isFinishedProcessing) { data.putInt(Codes.Extras.NUM_PROCESSING, backgroundProcessor.numProcessing); data.putInt(Codes.Extras.NUM_COMPLETED, backgroundProcessor.numCompleted); message.setData(data); if (mListener != null) { mListener.onUpdate(message); } DropboxSyncManager.getInstance(null).uploadMediaAsync(media); } } else { ((BatchCompleteJob) getOnBatchCompleteTask()).addThumbnail(entry); } } } catch (Exception e) { Logger.e(LOG, e); } return super.onStart(); } private void parseExif() { if(entry.mediaType.equals(MimeType.IMAGE)) { if (entry.fileAsset.source == Storage.Type.FILE_SYSTEM) { try { ExifInterface ei = new ExifInterface(entry.fileAsset.path); entry.exif.aperture = ei.getAttribute(ExifInterface.TAG_APERTURE); entry.exif.timestamp = ei.getAttribute(ExifInterface.TAG_DATETIME); entry.exif.exposure = ei.getAttribute(ExifInterface.TAG_EXPOSURE_TIME); entry.exif.flash = ei.getAttributeInt(ExifInterface.TAG_FLASH, -1); entry.exif.focalLength = ei.getAttributeInt(ExifInterface.TAG_FOCAL_LENGTH, -1); entry.exif.iso = ei.getAttribute(ExifInterface.TAG_ISO); entry.exif.make = ei.getAttribute(ExifInterface.TAG_MAKE); entry.exif.model = ei.getAttribute(ExifInterface.TAG_MODEL); entry.exif.orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); entry.exif.whiteBalance = ei.getAttributeInt(ExifInterface.TAG_WHITE_BALANCE, -1); entry.exif.width = ei.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, -1); entry.exif.height = ei.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, -1); } catch (IOException e) { Logger.e(LOG, e); } } else { //need exif reader for encrypted storage } } else if(entry.mediaType.startsWith(MimeType.VIDEO_BASE)) { MediaMetadataRetriever mmr = new MediaMetadataRetriever(); if (entry.fileAsset.source == Storage.Type.FILE_SYSTEM) { mmr.setDataSource(entry.fileAsset.path); } else if (entry.fileAsset.source == Storage.Type.IOCIPHER) { // there won't be any useful exif here, and the piping doesn't work for now anyhow /** try { ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); ParcelFileDescriptor.AutoCloseOutputStream acos = new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]); new PipeFeeder(new info.guardianproject.iocipher.FileInputStream(entry.fileAsset.path),acos).start(); mmr.setDataSource(pipe[0].getFileDescriptor()); } catch (Exception ioe) { Logger.e(LOG, ioe); mmr.release(); return; }*/ mmr.release(); return; } entry.exif.duration = Long.parseLong(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)); if (android.os.Build.VERSION.SDK_INT >= 14) { /* * these keys are min API 14 */ try { entry.exif.width = Integer.parseInt(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)); entry.exif.height = Integer.parseInt(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)); entry.exif.orientation = Integer.parseInt(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)); } catch(NumberFormatException e) { Logger.e(LOG, e); entry.exif.orientation = ExifInterface.ORIENTATION_NORMAL; } } //Logger.d(LOG, "VIDEO EXIF: " + entry.exif.asJson().toString()); mmr.release(); } } private void parseThumbnails() throws IOException { Bitmap b = null; if(entry.mediaType.startsWith(Models.IMedia.MimeType.VIDEO_BASE)) { if (entry.fileAsset.source == Storage.Type.FILE_SYSTEM) { b = MediaStore.Images.Thumbnails.getThumbnail(this.informaCam.getContentResolver(), entry.id, MediaStore.Images.Thumbnails.MICRO_KIND, null); if(b == null) { b = MediaStore.Images.Thumbnails.getThumbnail(this.informaCam.getContentResolver(), entry.id, MediaStore.Images.Thumbnails.MINI_KIND, null); } if(b == null) { MediaMetadataRetriever mmr = new MediaMetadataRetriever(); Bitmap b_ = null; mmr.setDataSource(entry.fileAsset.path); b_ = mmr.getFrameAtTime(); if(b_ == null) { } else { Logger.d(LOG, "got a video bitmap: (height " + b_.getHeight() + ")"); b = ImageUtility.createThumb(b_, new int[] {entry.exif.width, entry.exif.height}); } mmr.release(); } } else if (entry.fileAsset.source == Storage.Type.IOCIPHER) { info.guardianproject.iocipher.File fileThumb = new info.guardianproject.iocipher.File(entry.fileAsset.path + ".thumb.jpg"); if (fileThumb.exists()) { InputStream is = informaCam.ioService.getStream(fileThumb.getAbsolutePath(), entry.fileAsset.source); BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inSampleSize = 4; b = BitmapFactory.decodeStream(is, null, opts); } } } else if(entry.mediaType.equals(Models.IMedia.MimeType.IMAGE)) { InputStream is = informaCam.ioService.getStream(entry.fileAsset.path, entry.fileAsset.source); BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inSampleSize = 4; b = BitmapFactory.decodeStream(is, null, opts); } if(b != null) { String thumbnailFileName = entry.name + ".thumb"; if(entry.fileAsset.source == Storage.Type.IOCIPHER) { info.guardianproject.iocipher.File thumbnail = new info.guardianproject.iocipher.File(entry.originalHash, thumbnailFileName); informaCam.ioService.saveBlob(IOUtility.getBytesFromBitmap(b), thumbnail); entry.thumbnail = new IAsset(thumbnail.getAbsolutePath(),entry.fileAsset.source); } else { java.io.File thumbnail = new java.io.File(IOUtility.buildPublicPath(new String [] {"thumbnails"}), thumbnailFileName); try { informaCam.ioService.saveBlob(IOUtility.getBytesFromBitmap(b), thumbnail, true); } catch (IOException e) { //Logger.d(LOG, "WHAAAT?"); Logger.e(LOG, e); } entry.thumbnail = new IAsset(thumbnail.getAbsolutePath(),entry.fileAsset.source); } //b.recycle(); } } private void analyze() throws IOException, NoSuchAlgorithmException, JSONException { java.io.File file = null; if (entry.fileAsset.source == Storage.Type.IOCIPHER) file = new info.guardianproject.iocipher.File(entry.fileAsset.path); else file = new java.io.File(entry.fileAsset.path); if(!entry.isAvailable()) { entry = null; return; } entry.name = file.getName(); entry.fileAsset.name = entry.name; entry.size = file.length(); if (entry.fileAsset.source == Storage.Type.FILE_SYSTEM) entry.timeCaptured = file.lastModified(); // Questionable...? else entry.timeCaptured = new java.util.Date().getTime(); if (entry.fileAsset.source == Storage.Type.IOCIPHER) entry.originalHash = MediaHasher.hash(new info.guardianproject.iocipher.FileInputStream((info.guardianproject.iocipher.File) file), "SHA-1"); else entry.originalHash = MediaHasher.hash(file, "SHA-1"); if(entry.uri == null) { if (entry.fileAsset.source == Storage.Type.IOCIPHER) { entry.uri = Uri.parse(entry.authority).toString(); } else { entry.uri = IOUtility.getUriFromFile(informaCam, Uri.parse(entry.authority), file).toString(); } } //Logger.d(LOG, "analyzing: " + entry.asJson().toString()); // Make folder for assets according to preferences if(!entry.mediaType.equals(Models.IDCIMEntry.THUMBNAIL)) { if(entry.fileAsset.source == Storage.Type.IOCIPHER) { info.guardianproject.iocipher.File rootFolder = new info.guardianproject.iocipher.File("thumbnails"); try { if(!rootFolder.exists()) { rootFolder.mkdir(); } } catch(ExceptionInInitializerError e) { Logger.e(LOG, e); } } else { java.io.File rootFolder = new java.io.File(Storage.EXTERNAL_DIR, "thumbnails"); try { if(!rootFolder.exists()) { rootFolder.mkdir(); } } catch(ExceptionInInitializerError e) { Logger.e(LOG, e); } } parseExif(); parseThumbnails(); commit(); } } protected void commit() { //XXX: get preference here, save and delete original if encryptOriginals /* if((Boolean) informaCam.user.getPreference(IUser.ASSET_ENCRYPTION, false)) { Logger.d(LOG, "COPY AND DELETE..."); try { // IAsset publicAsset = new IAsset(entry.fileAsset); if(entry.fileAsset.copy(entry.fileAsset.source, Storage.Type.IOCIPHER, entry.originalHash)!=null) { // Logger.d(LOG, "public Asset to delete?\n" + publicAsset.asJson().toString()); if (entry.fileAsset.source == Storage.Type.FILE_SYSTEM) { informaCam.ioService.delete(entry.uri, Storage.Type.CONTENT_RESOLVER); } } } catch (Exception e) { Logger.e(LOG, e); } }*/ } }