package org.witness.informacam.models.media; import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SignatureException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.ConcurrentModificationException; import java.util.Date; import java.util.HashMap; import java.util.List; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.ByteArrayOutputStream; import org.spongycastle.openpgp.PGPException; import org.witness.informacam.InformaCam; import org.witness.informacam.R; import org.witness.informacam.crypto.EncryptionUtility; import org.witness.informacam.crypto.KeyUtility; import org.witness.informacam.informa.InformaService; import org.witness.informacam.informa.embed.ImageConstructor; import org.witness.informacam.informa.embed.VideoConstructor; import org.witness.informacam.json.JSONArray; import org.witness.informacam.json.JSONException; import org.witness.informacam.json.JSONObject; import org.witness.informacam.json.JSONTokener; import org.witness.informacam.models.Model; import org.witness.informacam.models.forms.IForm; import org.witness.informacam.models.j3m.IDCIMEntry; import org.witness.informacam.models.j3m.IData; import org.witness.informacam.models.j3m.IGenealogy; import org.witness.informacam.models.j3m.IIntakeData; import org.witness.informacam.models.j3m.IIntent; import org.witness.informacam.models.j3m.IRegionData; import org.witness.informacam.models.j3m.ISensorCapture; import org.witness.informacam.models.notifications.INotification; import org.witness.informacam.models.organizations.IOrganization; import org.witness.informacam.models.transport.ITransportStub; import org.witness.informacam.storage.IOUtility; import org.witness.informacam.transport.TransportUtility; import org.witness.informacam.ui.editors.IRegionDisplay; import org.witness.informacam.utils.Constants.App.Storage; import org.witness.informacam.utils.Constants.App.Storage.Type; import org.witness.informacam.utils.Constants.Codes; import org.witness.informacam.utils.Constants.IRegionDisplayListener; import org.witness.informacam.utils.Constants.Logger; import org.witness.informacam.utils.Constants.MetadataEmbededListener; import org.witness.informacam.utils.Constants.Models; import org.witness.informacam.utils.Constants.Models.IMedia.MimeType; import org.witness.informacam.utils.Constants.Suckers.CaptureEvent; import org.witness.informacam.utils.Constants.Suckers.Geo; import org.witness.informacam.utils.MediaHasher; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Base64; import android.util.Log; public class IMedia extends Model implements MetadataEmbededListener { public String rootFolder = null; public String _id = null; public String alias = null; public long lastEdited = 0L; public boolean isNew = false; public List<String> associatedCaches = null; public List<IRegion> associatedRegions = null; public int width = 0; public int height = 0; public IDCIMEntry dcimEntry = null; public IIntakeData intakeData = null; public IData data = null; public IIntent intent = null; public IGenealogy genealogy = null; public CharSequence detailsAsText = null; private Bitmap mThumbnail = null; private Handler mHandler = null; public Bitmap getBitmap(IAsset bitmapAsset) { try { return IOUtility.getBitmapFromFile(bitmapAsset.path, bitmapAsset.source, -1); } catch (IOException ioe) { return null; } } public Bitmap getBitmap(IAsset bitmapAsset, int size) { try { return IOUtility.getBitmapFromFile(bitmapAsset.path, bitmapAsset.source, size); } catch (IOException ioe) { return null; } } public Bitmap testImage () { return getThumbnail (64); } public boolean hasThumbnail () { return mThumbnail != null; } public Bitmap getThumbnail (int size) { if (mThumbnail == null && dcimEntry.thumbnail != null) mThumbnail = getBitmap(dcimEntry.thumbnail, size); return mThumbnail; } public boolean delete() { return InformaCam.getInstance().mediaManifest.removeMediaItem(this); } public IForm attachForm(Activity a, IForm form) throws InstantiationException, IllegalAccessException { IRegion region = addRegion(a, null); form = new IForm(form, a); return region.addForm(form); } public List<IForm> getForms(Activity a) throws InstantiationException, IllegalAccessException { IRegion region = getTopLevelRegion(); List<IForm> forms = new ArrayList<IForm>(); if (region != null) { for(IForm form : region.associatedForms) { byte[] answerBytes = InformaCam.getInstance().ioService.getBytes(form.answerPath, Type.IOCIPHER); forms.add(new IForm(form, a, answerBytes)); } } return forms; } public IForm getForm(Activity a) throws InstantiationException, IllegalAccessException { IRegion region = getTopLevelRegion(); IForm form = region.associatedForms.get(0); byte[] answerBytes = InformaCam.getInstance().ioService.getBytes(form.answerPath, Type.IOCIPHER); return new IForm(form, a, answerBytes); } public IRegion getTopLevelRegion() throws InstantiationException, IllegalAccessException { return getRegionAtRect(); } public List<IRegion> getInnerLevelRegions() { List<IRegion> innerLevelFormRegions = new ArrayList<IRegion>(); if (associatedRegions != null) { for(IRegion region : associatedRegions) { if(region.isInnerLevelRegion()) { innerLevelFormRegions.add(region); } } } return innerLevelFormRegions; } public IRegion getRegionAtRect() throws InstantiationException, IllegalAccessException { return getRegionAtRect(0, 0, 0, 0, -1, false); } public IRegion getRegionAtRect(IRegionDisplay regionDisplay) throws InstantiationException, IllegalAccessException { return getRegionAtRect(regionDisplay, 0); } public IRegion getRegionAtRect(IRegionDisplay regionDisplay, long timestamp) throws InstantiationException, IllegalAccessException { IRegionBounds bounds = regionDisplay.bounds; return getRegionAtRect(bounds.displayTop, bounds.displayLeft, bounds.displayWidth, bounds.displayHeight, timestamp, false); } public IRegion getRegionAtRect(int top, int left, int width, int height, long timestamp, boolean byRealHeight) throws InstantiationException, IllegalAccessException { if(associatedRegions != null) { for(IRegion region : associatedRegions) { IRegionBounds bounds = null; if(dcimEntry.mediaType.startsWith(MimeType.VIDEO_BASE)) { if (region instanceof IVideoRegion) { IVideoRegion videoRegion = new IVideoRegion(region); videoRegion = (IVideoRegion) region; bounds = videoRegion.getBoundsAtTime(timestamp); region = videoRegion; } } else { bounds = region.bounds; } if(byRealHeight) { if(bounds != null && bounds.top == top && bounds.left == left && bounds.width == width && bounds.height == height) { return region; } } else { if(bounds != null && bounds.displayTop == top && bounds.displayLeft == left && bounds.displayWidth == width && bounds.displayHeight == height) { return region; } } } } return null; } public List<IRegion> getRegionsByNamespace(String namespace) { List<IRegion> regionsWithForms = new ArrayList<IRegion>(); if(associatedRegions != null && associatedRegions.size() >0) { for(IRegion region : associatedRegions) { if(region.associatedForms.isEmpty()) { continue; } boolean regionHasForms = false; for(IForm form : region.associatedForms) { if(form.namespace.equals(namespace)) { regionHasForms = true; break; } } if(regionHasForms) { regionsWithForms.add(region); } } } return regionsWithForms; } public List<IRegion> getRegionsWithForms(List<String> omitableNamespaces) { List<IRegion> regionsWithForms = new ArrayList<IRegion>(); if(associatedRegions != null && associatedRegions.size() > 0) { for(IRegion region : associatedRegions) { if(region.associatedForms.isEmpty()) { continue; } boolean regionHasForms = false; if(omitableNamespaces == null) { regionHasForms = true; } else { for(IForm form : region.associatedForms) { if(!omitableNamespaces.contains(form.namespace)) { regionHasForms = true; break; } } } if(regionHasForms) { regionsWithForms.add(region); } } } return regionsWithForms; } public void save() throws InstantiationException, IllegalAccessException { InformaCam informaCam = InformaCam.getInstance(); informaCam.mediaManifest.getById(_id).inflate(asJson()); informaCam.saveState(informaCam.mediaManifest); } public boolean rename(String alias) { this.alias = alias; return true; } public IRegion addRegion(Activity activity, IRegionDisplayListener listener) throws InstantiationException, IllegalAccessException { try { return addRegion(activity, 0, 0, 0, 0, listener); } catch (JSONException e) { Logger.e(LOG, e); } return null; } public IRegion addRegion(Activity activity, int top, int left, int width, int height, IRegionDisplayListener listener) throws JSONException, InstantiationException, IllegalAccessException { return addRegion(activity, top, left, width, height, -1L, -1L, listener); } public IRegion addRegion(Activity activity, int top, int left, int width, int height, long startTime, long endTime, IRegionDisplayListener listener) throws JSONException, InstantiationException, IllegalAccessException { if(associatedRegions == null) { Logger.d(LOG, "initing associatedRegions"); associatedRegions = new ArrayList<IRegion>(); } IRegion region = new IRegion(); if(dcimEntry.mediaType.startsWith(MimeType.VIDEO_BASE)) { IVideoRegion videoRegion = new IVideoRegion(region); region = videoRegion; } region.init(activity, new IRegionBounds(top, left, width, height, startTime, endTime), listener); boolean startedByUs = false; if (InformaService.getInstance() == null) { startedByUs = true; int numTries = 5; int tryIdx = 0; while (InformaService.getInstance() == null && tryIdx < numTries) { try {Thread.sleep(1000);} catch(Exception e){} tryIdx++; } } if (InformaService.getInstance() != null) InformaService.getInstance().addRegion(region); associatedRegions.add(region); if(region.isInnerLevelRegion()) { assignInnerLevelRegionIndexes(); } //Logger.d(LOG, "added region " + region.asJson().toString() + "\nassociatedRegions size: " + associatedRegions.size()); return region; } public void assignInnerLevelRegionIndexes() { int r = 0; for(IRegion region : associatedRegions) { if(region.isInnerLevelRegion()) { region.index = r; r++; } } } public void removeRegion(IRegion region) { boolean wasInnerLevelRegion = region.isInnerLevelRegion(); region.delete(this); if(wasInnerLevelRegion) { assignInnerLevelRegionIndexes(); } } protected void mungeGenealogyAndIntent() { InformaCam informaCam = InformaCam.getInstance(); if(genealogy == null) { genealogy = new IGenealogy(); } genealogy.createdOnDevice = informaCam.user.pgpKeyFingerprint; genealogy.localMediaPath = rootFolder; if(intent == null) { intent = new IIntent(); } intent.alias = informaCam.user.alias; intent.pgpKeyFingerprint = informaCam.user.pgpKeyFingerprint; } @SuppressWarnings("unused") protected void mungeData() throws FileNotFoundException, InstantiationException, IllegalAccessException { if(data == null) { data = new IData(); } if(intakeData != null) { data.intakeData = intakeData; } if(associatedRegions != null) { for(IRegion region : associatedRegions) { IRegionData regionData = new IRegionData(new IRegion(region)); for(IForm form : region.associatedForms) { if(regionData.associatedForms != null) { if(data.userAppendedData == null) { data.userAppendedData = new ArrayList<IRegionData>(); } data.userAppendedData.add(regionData); } } } } } protected void mungeSensorLogs() { mungeSensorLogs(null); } public String getSimpleLocationString () { if (data == null || data.sensorCapture == null) { mungeSensorLogs (); } for (ISensorCapture sc : data.sensorCapture) { if (sc.sensorPlayback.has(Geo.Keys.GPS_COORDS)) { try { return sc.sensorPlayback.getString(Geo.Keys.GPS_COORDS); } catch (Exception e){} } } return null; } protected void mungeSensorLogs(Handler h) { if(data == null) { data = new IData(); } if(associatedCaches != null && associatedCaches.size() > 0) { synchronized(associatedCaches) { int progress = 0; int progressInterval = (int) (40/associatedCaches.size()); InformaCam informaCam = InformaCam.getInstance(); data.sensorCapture = new ArrayList<ISensorCapture>(); for(String ac : associatedCaches) { try { // get the data and loop through capture types InputStream isCache = informaCam.ioService.getStream(ac, Type.IOCIPHER); if (isCache == null) { Log.d(LOG,"cache was null: " + ac); continue; } JSONArray cache = ((JSONObject) new JSONTokener(isCache).nextValue()).getJSONArray(Models.LogCache.CACHE); for(int i=0; i<cache.length(); i++) { try { JSONObject entry = cache.getJSONObject(i); long ts = Long.parseLong((String) entry.keys().next()); JSONObject captureEvent = entry.getJSONObject(String.valueOf(ts)); if(captureEvent.has(CaptureEvent.Keys.TYPE)) { JSONArray captureTypes = captureEvent.getJSONArray(CaptureEvent.Keys.TYPE); for(int ct=0; ct<captureTypes.length(); ct++) { switch((Integer) captureTypes.get(ct)) { case CaptureEvent.SENSOR_PLAYBACK: data.sensorCapture.add(new ISensorCapture(ts, captureEvent)); break; case CaptureEvent.REGION_GENERATED: // Logger.d(LOG, "might want to reexamine this logpack:\n" + captureEvent.toString()); break; } } } else { data.sensorCapture.add(new ISensorCapture(ts, captureEvent)); } } catch (Exception e) { Logger.d(LOG,"error parsing cache event"); Logger.e(LOG, e); } } progress += progressInterval; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); } catch (IOException ioe) { Logger.d(LOG,"unable to get stream: " + ioe.toString()); } } } } } public IAsset export(Context context, Handler h) throws InstantiationException, IllegalAccessException, IOException, NoSuchAlgorithmException, SignatureException, NoSuchProviderException, PGPException { return export(context, h, null, true, true, false); } public IAsset export(Context context, Handler h, IOrganization organization) throws InstantiationException, IllegalAccessException, IOException, NoSuchAlgorithmException, SignatureException, NoSuchProviderException, PGPException { return export(context, h, organization, true, (organization == null), true); } public IAsset export(Context context, Handler h, IOrganization organization, boolean includeSensorLogs, boolean isLocalShare, boolean doSubmission) throws InstantiationException, IllegalAccessException, IOException, NoSuchAlgorithmException, SignatureException, PGPException, NoSuchProviderException { mHandler = h; int progress = 0; InformaCam informaCam = InformaCam.getInstance(); INotification notification = new INotification(); notification.icon = dcimEntry.thumbnail; // create data package if(data == null) { data = new IData(); } data.exif = dcimEntry.exif; progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); if (includeSensorLogs) mungeSensorLogs(); progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); mungeData(); progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); mungeGenealogyAndIntent(); progress += 20; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); notification.label = context.getString(R.string.export); String mimeType = dcimEntry.mediaType.equals(MimeType.IMAGE) ? context.getString(R.string.image) :context.getString(R.string.video); if(dcimEntry.mediaType.equals(MimeType.LOG)) { mimeType = context.getString(R.string.log); } notification.content = context.getString(R.string.you_exported_this_x, mimeType); if(organization != null) { intent.intendedDestination = organization.organizationName; notification.content = context.getString(R.string.you_exported_this_x_to_x, mimeType, organization.organizationName); } progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); JSONObject j3mObject = null; j3mObject = new JSONObject(); JSONObject j3m = new JSONObject(); j3m.put(Models.IMedia.j3m.DATA, data.asJson()); j3m.put(Models.IMedia.j3m.GENEALOGY, genealogy.asJson()); j3m.put(Models.IMedia.j3m.INTENT, intent.asJson()); info.guardianproject.iocipher.File fileTmp = new info.guardianproject.iocipher.File("tmp-export-" + _id); info.guardianproject.iocipher.FileWriter fwTmp = new info.guardianproject.iocipher.FileWriter(fileTmp); j3m.write(fwTmp); fwTmp.flush(); fwTmp.close(); ByteArrayOutputStream bSig = new ByteArrayOutputStream(); informaCam.signatureService.signData(new info.guardianproject.iocipher.FileInputStream(fileTmp),bSig); fileTmp.delete(); j3mObject.put(Models.IMedia.j3m.SIGNATURE, new String(bSig.toByteArray())); j3mObject.put(Models.IMedia.j3m.J3M, j3m); ITransportStub submission = null; int exportDestination = dcimEntry.fileAsset.source; if (isLocalShare) exportDestination = Type.FILE_SYSTEM; IAsset j3mAsset = addAsset(dcimEntry.name + ".j3m", exportDestination); OutputStream os = null; if (j3mAsset.source == Type.FILE_SYSTEM) os = new java.io.FileOutputStream(j3mAsset.path); else if (j3mAsset.source == Type.IOCIPHER) os = new info.guardianproject.iocipher.FileOutputStream(j3mAsset.path); ByteArrayInputStream is = new ByteArrayInputStream(j3mObject.toString().getBytes()); // encrypt if the organization is not null if(organization != null) { EncryptionUtility.encrypt(is, os, Base64.encode(informaCam.ioService.getBytes(organization.publicKey, Type.IOCIPHER), Base64.DEFAULT)); } else { IOUtils.copyLarge(is, os); } os.flush(); os.close(); progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); String exportFileName = this.dcimEntry.name; notification.generateId(); notification.mediaId = this._id; notification.type = Models.INotification.Type.EXPORTED_MEDIA; if(organization != null && doSubmission) { notification.taskComplete = false; informaCam.addNotification(notification, h); submission = new ITransportStub(organization, notification); } IAsset exportAsset = new IAsset(exportDestination); exportAsset.name = exportFileName; exportAsset.source = exportDestination; exportAsset.path = IOUtility.buildPublicPath(new String[] { exportAsset.name }); if(this.dcimEntry.mediaType.equals(Models.IMedia.MimeType.VIDEO_MP4)) { exportAsset.name = exportAsset.name.replace(".mp4", ".mkv"); exportAsset.path = exportAsset.path.replace(".mp4", ".mkv"); exportAsset.name = exportAsset.name.replace(".ts", ".mkv"); exportAsset.path = exportAsset.path.replace(".ts", ".mkv"); } if(this.dcimEntry.mediaType.equals(Models.IMedia.MimeType.VIDEO_3GPP)) { exportAsset.name = exportAsset.name.replace(".3gp", ".mkv"); exportAsset.path = exportAsset.path.replace(".3gp", ".mkv"); } constructExport(exportAsset, submission); if(submission != null) { submission.setAsset(exportAsset, dcimEntry.mediaType, exportDestination); } informaCam.addNotification(notification, h); progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); return exportAsset; } private void constructExport(IAsset destinationAsset, ITransportStub submission) throws IOException { if(dcimEntry.mediaType.equals(MimeType.IMAGE)) { @SuppressWarnings("unused") ImageConstructor ic = new ImageConstructor(this, destinationAsset, submission); } else if(dcimEntry.mediaType.startsWith(MimeType.VIDEO_BASE)) { @SuppressWarnings("unused") VideoConstructor vc = new VideoConstructor(InformaCam.getInstance(), this, destinationAsset, submission); } } public String exportHash() { //generate public hash id from values String creatorHash = genealogy.createdOnDevice; StringBuffer mediaHash = new StringBuffer(); for(String mHash : genealogy.hashes) { mediaHash.append(mHash); } MessageDigest md; try { md = MessageDigest.getInstance("SHA-1"); md.update((creatorHash + mediaHash.toString()).getBytes()); byte[] byteData = md.digest(); StringBuffer hexString = new StringBuffer(); for (int i=0;i<byteData.length;i++) { String hex=Integer.toHexString(0xff & byteData[i]); if(hex.length()==1) hexString.append('0'); hexString.append(hex); } return hexString.toString(); } catch (NoSuchAlgorithmException e) { Logger.e(LOG, e); } return null; } public boolean exportJ3M(Context context, Handler h, IOrganization organization, boolean share) throws FileNotFoundException, InstantiationException, IllegalAccessException { // Logger.d(LOG, "EXPORTING A MEDIA ENTRY: " + _id); // Logger.d(LOG, "ORIGINAL ASSET SETTINGS: " + dcimEntry.fileAsset.asJson().toString()); System.gc(); mHandler = h; int progress = 0; InformaCam informaCam = InformaCam.getInstance(); INotification notification = new INotification(); notification.icon = dcimEntry.thumbnail; // create data package if(data == null) { data = new IData(); } data.exif = dcimEntry.exif; progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); mungeSensorLogs(); progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); mungeData(); progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); mungeGenealogyAndIntent(); progress += 20; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); notification.label = context.getString(R.string.export); String mimeType = dcimEntry.mediaType.equals(MimeType.IMAGE) ? context.getString(R.string.image) :context.getString(R.string.video); if(dcimEntry.mediaType.equals(MimeType.LOG)) { mimeType = context.getString(R.string.log); } notification.content = context.getString(R.string.you_exported_this_x, mimeType); if(organization != null) { intent.intendedDestination = organization.organizationName; notification.content = context.getString(R.string.you_exported_this_x_to_x, mimeType, organization.organizationName); } progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); JSONObject j3mObject = null; try { j3mObject = new JSONObject(); JSONObject j3m = new JSONObject(); j3m.put(Models.IMedia.j3m.DATA, data.asJson()); j3m.put(Models.IMedia.j3m.GENEALOGY, genealogy.asJson()); j3m.put(Models.IMedia.j3m.INTENT, intent.asJson()); byte[] sig = informaCam.signatureService.signData(j3m.toString().getBytes()); j3mObject.put(Models.IMedia.j3m.SIGNATURE, new String(sig)); j3mObject.put(Models.IMedia.j3m.J3M, j3m); IAsset j3mAsset = addAsset(dcimEntry.name + ".j3m", dcimEntry.fileAsset.source); progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); notification.generateId(); notification.mediaId = this._id; OutputStream os = null; if (j3mAsset.source == Type.FILE_SYSTEM) os = new java.io.FileOutputStream(j3mAsset.path); else if (j3mAsset.source == Type.IOCIPHER) os = new info.guardianproject.iocipher.FileOutputStream(j3mAsset.path); //os = new Base64OutputStream(new GZIPOutputStream(os), Base64.DEFAULT); ByteArrayInputStream is = new ByteArrayInputStream(j3mObject.toString().getBytes()); // encrypt if the organization is not null if(organization != null) { EncryptionUtility.encrypt(is, os, Base64.encode(informaCam.ioService.getBytes(organization.publicKey, Type.IOCIPHER), Base64.DEFAULT)); } else { IOUtils.copyLarge(is, os); } os.flush(); os.close(); ITransportStub submission = null; if(share) { notification.type = Models.INotification.Type.SHARED_MEDIA; } else { notification.type = Models.INotification.Type.EXPORTED_MEDIA; if(organization != null) { notification.taskComplete = false; informaCam.addNotification(notification, h); submission = new ITransportStub(organization, notification); submission.setAsset(j3mAsset, dcimEntry.mediaType, Storage.Type.IOCIPHER); } } onMetadataEmbeded(j3mAsset); progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); } catch (JSONException e) { Logger.e(LOG, e); return false; } catch (ConcurrentModificationException e) { Logger.e(LOG, e); return false; } catch (Exception e) { Logger.e(LOG, e); return false; } return true; } public boolean exportCSV(Context context, Handler h, IOrganization organization, boolean share) throws FileNotFoundException, InstantiationException, IllegalAccessException { // Logger.d(LOG, "EXPORTING A MEDIA ENTRY: " + _id); // Logger.d(LOG, "ORIGINAL ASSET SETTINGS: " + dcimEntry.fileAsset.asJson().toString()); System.gc(); mHandler = h; int progress = 0; InformaCam informaCam = InformaCam.getInstance(); INotification notification = new INotification(); notification.icon = dcimEntry.thumbnail; notification.label = context.getString(R.string.export); String mimeType = dcimEntry.mediaType.equals(MimeType.IMAGE) ? context.getString(R.string.image) :context.getString(R.string.video); if(dcimEntry.mediaType.equals(MimeType.LOG)) { mimeType = context.getString(R.string.log); } notification.content = context.getString(R.string.you_exported_this_x, mimeType); if(organization != null) { intent.intendedDestination = organization.organizationName; notification.content = context.getString(R.string.you_exported_this_x_to_x, mimeType, organization.organizationName); } progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); try { IAsset csvAsset = addAsset(dcimEntry.name + ".csv", dcimEntry.fileAsset.source); progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); notification.generateId(); notification.mediaId = this._id; String csv = buildCSV(context, h); OutputStream os = null; if (csvAsset.source == Type.FILE_SYSTEM) os = new java.io.FileOutputStream(csvAsset.path); else if (csvAsset.source == Type.IOCIPHER) os = new info.guardianproject.iocipher.FileOutputStream(csvAsset.path); ByteArrayInputStream is = new ByteArrayInputStream(csv.getBytes()); // encrypt if the organization is not null if(organization != null) { EncryptionUtility.encrypt(is, os, Base64.encode(informaCam.ioService.getBytes(organization.publicKey, Type.IOCIPHER), Base64.DEFAULT)); } else { IOUtils.copyLarge(is, os); } os.flush(); os.close(); ITransportStub submission = null; if(share) { notification.type = Models.INotification.Type.SHARED_MEDIA; } else { notification.type = Models.INotification.Type.EXPORTED_MEDIA; if(organization != null) { notification.taskComplete = false; informaCam.addNotification(notification, h); submission = new ITransportStub(organization, notification); submission.setAsset(csvAsset, dcimEntry.mediaType, Storage.Type.IOCIPHER); } } onMetadataEmbeded(csvAsset); progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); } catch (JSONException e) { Logger.e(LOG, e); return false; } catch (ConcurrentModificationException e) { Logger.e(LOG, e); return false; } catch (Exception e) { Logger.e(LOG, e); return false; } return true; } public String buildSummary(Context context, Handler h) throws FileNotFoundException, InstantiationException, IllegalAccessException { System.gc(); int progress = 0; InformaCam informaCam = InformaCam.getInstance(); INotification notification = new INotification(); notification.icon = dcimEntry.thumbnail; // create data package if(data == null) { data = new IData(); } data.exif = dcimEntry.exif; progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); mungeSensorLogs(); progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); mungeData(); progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); mungeGenealogyAndIntent(); progress += 20; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); /* String mimeType = dcimEntry.mediaType.equals(MimeType.IMAGE) ? context.getString(R.string.image) :context.getString(R.string.video); if(dcimEntry.mediaType.equals(MimeType.LOG)) { mimeType = context.getString(R.string.log); } */ progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); StringBuffer result = new StringBuffer(); try { result.append("Media ID: ").append(this._id).append("\n"); result.append("Device Key: ").append(genealogy.createdOnDevice).append("\n"); result.append("Date Created: ").append(new Date(genealogy.dateCreated).toLocaleString()).append("\n"); if (data.exif.make != null) result.append("Device: ").append(data.exif.make + ' ' + data.exif.model).append("\n"); if (data.exif.location != null && data.exif.location.length > 0) result.append("Location: ").append(data.exif.location[0]+ "," + data.exif.location[1]).append("\n"); return result.toString(); } catch (JSONException e) { Logger.e(LOG, e); } catch (Exception e) { Logger.e(LOG, e); } return null; } public String buildJ3M(Context context, boolean signData, Handler h) throws FileNotFoundException, InstantiationException, IllegalAccessException { System.gc(); int progress = 0; InformaCam informaCam = InformaCam.getInstance(); INotification notification = new INotification(); notification.icon = dcimEntry.thumbnail; // create data package if(data == null) { data = new IData(); } data.exif = dcimEntry.exif; progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); mungeSensorLogs(); progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); mungeData(); progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); mungeGenealogyAndIntent(); progress += 20; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); /* String mimeType = dcimEntry.mediaType.equals(MimeType.IMAGE) ? context.getString(R.string.image) :context.getString(R.string.video); if(dcimEntry.mediaType.equals(MimeType.LOG)) { mimeType = context.getString(R.string.log); } */ progress += 10; sendMessage(Codes.Keys.UI.PROGRESS, progress, h); JSONObject j3mObject = null; try { j3mObject = new JSONObject(); JSONObject j3m = new JSONObject(); j3m.put(Models.IMedia.j3m.DATA, data.asJson()); j3m.put(Models.IMedia.j3m.GENEALOGY, genealogy.asJson()); j3m.put(Models.IMedia.j3m.INTENT, intent.asJson()); if (signData) { byte[] sig = informaCam.signatureService.signData(j3m.toString().getBytes()); j3mObject.put(Models.IMedia.j3m.SIGNATURE, new String(sig)); } j3mObject.put(Models.IMedia.j3m.J3M, j3m); return j3mObject.toString(); } catch (JSONException e) { Logger.e(LOG, e); } catch (Exception e) { Logger.e(LOG, e); } return null; } public String buildCSV(Context context, Handler h) throws FileNotFoundException, InstantiationException, IllegalAccessException { StringBuffer result = new StringBuffer(); try { String j3m = buildJ3M(context, false, null); ArrayList<ISensorCapture> listSensorEvents = new ArrayList<ISensorCapture>(data.sensorCapture); Collections.sort(listSensorEvents,new Comparator<ISensorCapture>() { @Override public int compare(ISensorCapture lhs, ISensorCapture rhs) { if (lhs.timestamp < rhs.timestamp) return -1; else if (lhs.timestamp == rhs.timestamp) return 0; else return 1; } }); DateFormat dateFormat = SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG); //do charts final String[] sensorTypes = {"datetime","gps_coords","gps_accuracy","lightMeterValue","pressureHPAOrMBAR","pitch","roll","azimuth","acc_x","acc_y","acc_z","bluetoothDeviceAddress","cellTowerId","visibleWifiNetworks"}; HashMap<String,String> hmRow = new HashMap<String,String>(); for (String sensorType : sensorTypes) { result.append(sensorType).append(','); hmRow.put(sensorType, ""); } result.append("\n"); for (ISensorCapture sensor : listSensorEvents) { hmRow.put("datetime", dateFormat.format(sensor.timestamp)); for (String sensorType : sensorTypes) { if (sensor.sensorPlayback.has(sensorType)) { String val = sensor.sensorPlayback.get(sensorType).toString(); val = val.replace(',',' ');//replace commas with spaces for csv output hmRow.put(sensorType, val); } } for (String sensorType : sensorTypes) { result.append(hmRow.get(sensorType)).append(','); } result.append("\n"); } } catch (Exception e) { e.printStackTrace(); } return result.toString(); } public String renderDetailsAsText(int depth) { StringBuffer details = new StringBuffer(); switch(depth) { case 1: if(this.alias != null) { details.append(this.alias); } details.append(this._id); //Logger.d(LOG, this.asJson().toString()); break; } return details.toString(); } public boolean analyze() throws IOException { isNew = true; if(genealogy == null) { genealogy = new IGenealogy(); } try { info.guardianproject.iocipher.File rootFolder = new info.guardianproject.iocipher.File("."); this.rootFolder = rootFolder.getAbsolutePath(); if(!rootFolder.exists()) { rootFolder.mkdir(); } return true; } catch (ExceptionInInitializerError e) {} return false; } public String generateId(String seed) { try { return MediaHasher.hash(KeyUtility.generatePassword(seed.getBytes()).getBytes(), "MD5"); } catch (NoSuchAlgorithmException e) { Logger.e(LOG, e); } catch (IOException e) { Logger.e(LOG, e); } return seed; } protected void sendMessage(String key, String what, Handler h) { Bundle b = new Bundle(); b.putString(key, what); Message msg = new Message(); msg.setData(b); if(h != null) { h.sendMessage(msg); } } protected void sendMessage(String key, int what, Handler h) { Bundle b = new Bundle(); b.putInt(key, what); Message msg = new Message(); msg.setData(b); if(h != null) { h.sendMessage(msg); } } protected void reset() throws InstantiationException, IllegalAccessException { data.userAppendedData = null; data.sensorCapture = null; save(); } @Override public void onMediaReadyForTransport(final ITransportStub transportStub) { if(!transportStub.organization.keyReceived) { InformaCam informaCam = InformaCam.getInstance(); INotification notification = new INotification(informaCam.getString(R.string.key_sent), informaCam.getString(R.string.you_have_sent_your_credentials_to_x, transportStub.organization.organizationName), Models.INotification.Type.NEW_KEY); notification.taskComplete = false; informaCam.addNotification(notification, null); ITransportStub credentialStub = new ITransportStub(transportStub.organization, notification); credentialStub.setAsset(Models.IUser.PUBLIC_CREDENTIALS, Models.IUser.PUBLIC_CREDENTIALS, MimeType.ZIP, Type.IOCIPHER); credentialStub.callbackCode = Models.ITransportStub.CallbackCodes.UPDATE_ORGANIZATION_HAS_KEY; TransportUtility.initTransport(credentialStub); } TransportUtility.initTransport(transportStub); } @Override public void onMetadataEmbeded(IAsset version) { try { reset(); Bundle b = new Bundle(); b.putString(Models.IMedia.VERSION, version.path); b.putString(Models.IMedia._ID, this._id); Message msg = new Message(); msg.setData(b); if(mHandler != null) { mHandler.sendMessage(msg); } } catch (Exception e) { Logger.d(LOG,"unable to process IAsset: " + version.name); Logger.e(LOG,e); } } public IAsset addAsset(String name, int exportDestination) { String path = IOUtility.buildPath(new String[] { rootFolder, name }); if(exportDestination == Type.FILE_SYSTEM) { path = IOUtility.buildPublicPath(new String[] { name }); } return new IAsset(path, exportDestination, name); } public IAsset getAsset(String name) { InformaCam informaCam = InformaCam.getInstance(); String path = IOUtility.buildPath(new String[] { rootFolder, name }); int source = dcimEntry.fileAsset.source; if(source == Type.FILE_SYSTEM) { path = IOUtility.buildPublicPath(new String[] { name }); } if(informaCam.ioService.getBytes(path, source) == null) { return null; } return new IAsset(path, source, name); } }