diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..ef8aef7
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+Lifebox_0.1.4
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..96cc43e
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..e7bedf3
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..97626ba
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..21d9eda
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..e95f22e
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..3b31283
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..a04bdee
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..4680383
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..7f68460
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 753f882..ec62182 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,3 @@
-# LifeBoxApp
\ No newline at end of file
+# LifeBoxApp
+
+LifeBoxApp is a lifelogging and sousveillance app that captures audio 24/7 on your Android device and uploads it to your personal Dropbox account.
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..9fdc241
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,32 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 16
+ buildToolsVersion "23.0.3"
+
+ defaultConfig {
+ applicationId "com.lifebox.lifeboxapp"
+ minSdkVersion 16
+ targetSdkVersion 16
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_5
+ targetCompatibility JavaVersion.VERSION_1_5
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+ }
+ }
+}
+
+dependencies {
+ compile 'com.android.support:support-v4:18.0.0'
+ compile files('libs/dropbox-android-sdk-1.5.1.jar')
+ compile files('libs/httpclient-4.0.3.jar')
+ compile files('libs/httpcore-4.0.1.jar')
+ compile files('libs/httpmime-4.0.3.jar')
+ compile files('libs/json_simple-1.1.jar')
+}
diff --git a/app/libs/commons-codec-1.10.jar b/app/libs/commons-codec-1.10.jar
new file mode 100644
index 0000000..1d7417c
Binary files /dev/null and b/app/libs/commons-codec-1.10.jar differ
diff --git a/app/libs/commons-logging-1.1.1.jar b/app/libs/commons-logging-1.1.1.jar
new file mode 100644
index 0000000..8758a96
Binary files /dev/null and b/app/libs/commons-logging-1.1.1.jar differ
diff --git a/app/libs/dropbox-android-sdk-1.5.1.jar b/app/libs/dropbox-android-sdk-1.5.1.jar
new file mode 100644
index 0000000..3d9c2fc
Binary files /dev/null and b/app/libs/dropbox-android-sdk-1.5.1.jar differ
diff --git a/app/libs/httpclient-4.0.3.jar b/app/libs/httpclient-4.0.3.jar
new file mode 100644
index 0000000..fd0d377
Binary files /dev/null and b/app/libs/httpclient-4.0.3.jar differ
diff --git a/app/libs/httpcore-4.0.1.jar b/app/libs/httpcore-4.0.1.jar
new file mode 100644
index 0000000..4638daa
Binary files /dev/null and b/app/libs/httpcore-4.0.1.jar differ
diff --git a/app/libs/httpmime-4.0.3.jar b/app/libs/httpmime-4.0.3.jar
new file mode 100644
index 0000000..0dfd331
Binary files /dev/null and b/app/libs/httpmime-4.0.3.jar differ
diff --git a/app/libs/json_simple-1.1.jar b/app/libs/json_simple-1.1.jar
new file mode 100644
index 0000000..f395f41
Binary files /dev/null and b/app/libs/json_simple-1.1.jar differ
diff --git a/app/libs/libGoogleAnalytics.jar b/app/libs/libGoogleAnalytics.jar
new file mode 100644
index 0000000..78d47a7
Binary files /dev/null and b/app/libs/libGoogleAnalytics.jar differ
diff --git a/app/lint.xml b/app/lint.xml
new file mode 100644
index 0000000..8423c0e
--- /dev/null
+++ b/app/lint.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ef9a678
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/assets/icon.jpg b/app/src/main/assets/icon.jpg
new file mode 100644
index 0000000..efeda36
Binary files /dev/null and b/app/src/main/assets/icon.jpg differ
diff --git a/app/src/main/assets/logo.gif b/app/src/main/assets/logo.gif
new file mode 100644
index 0000000..e27d861
Binary files /dev/null and b/app/src/main/assets/logo.gif differ
diff --git a/app/src/main/java/com/lifebox/lifeboxapp/Crypto.java b/app/src/main/java/com/lifebox/lifeboxapp/Crypto.java
new file mode 100644
index 0000000..f29f7ce
--- /dev/null
+++ b/app/src/main/java/com/lifebox/lifeboxapp/Crypto.java
@@ -0,0 +1,50 @@
+package com.lifebox.lifeboxapp;
+
+import android.util.Base64;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+public class Crypto {
+ private byte[] keyBytes;
+
+ public Crypto(String key) throws Exception {
+ keyBytes = Base64ToByte(key);
+ }
+
+ public byte[] encrypt(byte[] plainText) throws Exception {
+ Cipher cipher = getCipher(Cipher.ENCRYPT_MODE);
+ return cipher.doFinal(plainText);
+ }
+
+ public byte[] decrypt(byte[] encrypted) throws Exception {
+ Cipher cipher = getCipher(Cipher.DECRYPT_MODE);
+ return cipher.doFinal(encrypted);
+ }
+
+ private Cipher getCipher(int cipherMode) throws Exception {
+ String encryptionAlgorithm = "AES";
+ SecretKeySpec keySpecification = new SecretKeySpec(keyBytes, encryptionAlgorithm);
+ Cipher cipher = Cipher.getInstance(encryptionAlgorithm);
+ cipher.init(cipherMode, keySpecification);
+ return cipher;
+ }
+
+ public static String makeKey(String password, String salt) throws Exception {
+ PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes("UTF-8"), 1000, 128);
+ SecretKeyFactory skf;
+ skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+ byte[] key = skf.generateSecret(spec).getEncoded();
+ return ByteToBase64(key);
+ }
+
+ public static String ByteToBase64(byte[] input) {
+ return Base64.encodeToString(input, Base64.DEFAULT);
+ }
+
+ public static byte[] Base64ToByte(String input) {
+ return Base64.decode(input, Base64.DEFAULT);
+ }
+}
diff --git a/app/src/main/java/com/lifebox/lifeboxapp/Installation.java b/app/src/main/java/com/lifebox/lifeboxapp/Installation.java
new file mode 100644
index 0000000..5675d1f
--- /dev/null
+++ b/app/src/main/java/com/lifebox/lifeboxapp/Installation.java
@@ -0,0 +1,45 @@
+// http://android-developers.blogspot.com.au/2011/03/identifying-app-installations.html
+
+package com.lifebox.lifeboxapp;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.UUID;
+
+import android.content.Context;
+
+public class Installation {
+ private static String sID = null;
+ private static final String INSTALLATION = "INSTALLATION";
+
+ public synchronized static String id(Context context) {
+ if (sID == null) {
+ File installation = new File(context.getFilesDir(), INSTALLATION);
+ try {
+ if (!installation.exists())
+ writeInstallationFile(installation);
+ sID = readInstallationFile(installation);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return sID;
+ }
+
+ private static String readInstallationFile(File installation) throws IOException {
+ RandomAccessFile f = new RandomAccessFile(installation, "r");
+ byte[] bytes = new byte[(int) f.length()];
+ f.readFully(bytes);
+ f.close();
+ return new String(bytes);
+ }
+
+ private static void writeInstallationFile(File installation) throws IOException {
+ FileOutputStream out = new FileOutputStream(installation);
+ String id = UUID.randomUUID().toString();
+ out.write(id.getBytes());
+ out.close();
+ }
+}
diff --git a/app/src/main/java/com/lifebox/lifeboxapp/LifeBoxAppMain.java b/app/src/main/java/com/lifebox/lifeboxapp/LifeBoxAppMain.java
new file mode 100644
index 0000000..b437ae7
--- /dev/null
+++ b/app/src/main/java/com/lifebox/lifeboxapp/LifeBoxAppMain.java
@@ -0,0 +1,407 @@
+package com.lifebox.lifeboxapp;
+
+import java.io.File;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.content.LocalBroadcastManager;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.Window;
+import android.widget.Button;
+
+import com.dropbox.client2.DropboxAPI;
+import com.dropbox.client2.android.AndroidAuthSession;
+import com.dropbox.client2.android.AuthActivity;
+import com.dropbox.client2.session.AccessTokenPair;
+import com.dropbox.client2.session.AppKeyPair;
+import com.dropbox.client2.session.Session.AccessType;
+import com.dropbox.client2.session.TokenPair;
+
+public class LifeBoxAppMain extends Activity {
+
+ private class DataUpdateReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(RecordingService.REDRAW_MAIN)) {
+ redraw_display();
+ }
+ }
+ }
+
+ private DataUpdateReceiver dataUpdateReceiver;
+
+ final static private String APP_KEY = "INSERT_APP_KEY";
+ final static private String APP_SECRET = "INSERT_APP_KEY";
+ final static private AccessType ACCESS_TYPE = AccessType.APP_FOLDER;
+ final static private String ACCOUNT_PREFS_NAME = "prefs";
+ final static private String ACCESS_KEY_NAME = "ACCESS_KEY";
+ final static private String ACCESS_SECRET_NAME = "ACCESS_SECRET";
+
+ private DropboxAPI mDBApi;
+
+ private boolean mLoggedIn = false;
+
+ private Context context = this;
+
+ private String _pathFolder = "";
+ protected ConnectivityManager connManager;
+ protected NetworkInfo mWifi;
+
+ //private RecordingServiceBroadcastReceiver intentReceiver = new RecordingServiceBroadcastReceiver();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+
+ super.onCreate(savedInstanceState);
+
+ this.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.main);
+
+ mDBApi = new DropboxAPI(buildSession());
+ _pathFolder = getExternalFilesDir(null).toString();
+ connManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
+
+ checkAppKeySetup();
+
+ _pathFolder = getExternalFilesDir(null).toString();
+
+ File dir = new File(_pathFolder);
+ for (File child : dir.listFiles()) {
+ if (".".equals(child.getName()) || "..".equals(child.getName())) {
+ continue;
+ }
+ if (child.getName().startsWith("incomplete")) {
+ child.delete();
+ }
+ }
+
+ connManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
+
+ checkAppKeySetup();
+
+ Button mSubmit = (Button) findViewById(R.id.auth_button);
+ Button mUpload = (Button) findViewById(R.id.upload);
+ Button mSettings = (Button) findViewById(R.id.settings);
+ Button mAbout = (Button) findViewById(R.id.about);
+
+ mSubmit.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ if (mLoggedIn) {
+ logOut();
+ } else {
+ mDBApi.getSession()
+ .startAuthentication(LifeBoxAppMain.this);
+ }
+ }
+ });
+
+ mUpload.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ uploaderStart();
+ }
+ });
+
+ mSettings.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ Intent myIntent = new Intent(LifeBoxAppMain.this,
+ Settings.class);
+ LifeBoxAppMain.this.startActivity(myIntent);
+ }
+ });
+
+ mAbout.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ String url = "https://clintidau.github.io/LifeBoxApp/";
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setData(Uri.parse(url));
+ startActivity(i);
+ }
+ });
+
+ mLoggedIn = mDBApi.getSession().isLinked();
+
+ redraw_display();
+ }
+
+ private boolean isMyServiceRunning() {
+ ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
+ for (RunningServiceInfo service : manager
+ .getRunningServices(Integer.MAX_VALUE)) {
+ if (RecordingService.class.getName().equals(
+ service.service.getClassName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ AndroidAuthSession session = mDBApi.getSession();
+
+ if (session.authenticationSuccessful()) {
+ try {
+ session.finishAuthentication();
+ TokenPair tokens = session.getAccessTokenPair();
+ storeKeys(tokens.key, tokens.secret);
+ mLoggedIn = true;
+ redraw_display();
+ }
+ catch (Exception e) {e.printStackTrace();}
+ }
+
+ if (!isMyServiceRunning()) {
+ try {
+ startService(new Intent(LifeBoxAppMain.this,
+ RecordingService.class));
+ }
+ catch (Exception e) {e.printStackTrace();}
+ }
+
+ if (dataUpdateReceiver == null)
+ dataUpdateReceiver = new DataUpdateReceiver();
+ IntentFilter intentFilter = new IntentFilter(RecordingService.REDRAW_MAIN);
+ LocalBroadcastManager.getInstance(this).registerReceiver(dataUpdateReceiver, intentFilter);
+ redraw_display();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (dataUpdateReceiver != null)
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(dataUpdateReceiver);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onStop() {
+ /*try {
+ unregisterReceiver(intentReceiver);
+ }
+ catch (Exception e) {e.printStackTrace();}
+ */
+ super.onStop();
+ }
+
+ private void logOut() {
+ mDBApi.getSession().unlink();
+ clearKeys();
+ mLoggedIn = false;
+ redraw_display();
+ }
+
+ private void redraw_display() {
+ try {
+ Button mSubmit = (Button) findViewById(R.id.auth_button);
+ Button mUpload = (Button) findViewById(R.id.upload);
+ final Button mRecord = (Button) findViewById(R.id.record_button);
+ final Button mStop = (Button) findViewById(R.id.stop_button);
+ final Button mAutomatic = (Button) findViewById(R.id.automatic_button);
+
+ if (mLoggedIn)
+ mSubmit.setVisibility(android.view.View.GONE);
+ else
+ mSubmit.setVisibility(android.view.View.VISIBLE);
+
+ if (onlyWhenScreenOff()) {
+ mRecord.setVisibility(android.view.View.GONE);
+ mStop.setVisibility(android.view.View.GONE);
+ mAutomatic.setVisibility(android.view.View.VISIBLE);
+ }
+ else {
+ mAutomatic.setVisibility(android.view.View.GONE);
+ if (recordingStatus()) {
+ mRecord.setVisibility(android.view.View.GONE);
+ mStop.setVisibility(android.view.View.VISIBLE);
+ } else {
+ mRecord.setVisibility(android.view.View.VISIBLE);
+ mStop.setVisibility(android.view.View.GONE);
+ }
+ mRecord.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mRecord.setEnabled(false);
+ mStop.setEnabled(false);
+
+ LocalBroadcastManager.getInstance(context).sendBroadcast(
+ new Intent(RecordingService.RECORDING_START));
+
+ Handler handler = new Handler();
+ handler.postDelayed(new Runnable() {
+ public void run() {
+ mRecord.setEnabled(true);
+ mStop.setEnabled(true);
+ redraw_display();
+ }
+ }, 1000);
+ }
+ });
+ mStop.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ mRecord.setEnabled(false);
+ mStop.setEnabled(false);
+
+ LocalBroadcastManager.getInstance(context).sendBroadcast(
+ new Intent(RecordingService.RECORDING_STOP));
+
+ Handler handler = new Handler();
+ handler.postDelayed(new Runnable() {
+ public void run() {
+ mRecord.setEnabled(true);
+ mStop.setEnabled(true);
+ redraw_display();
+ }
+ }, 1000);
+ }
+ });
+ }
+
+ mWifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ Button mWifiStatus = (Button)findViewById(R.id.wifi_button);
+ if (mWifi.isConnected()) {
+ mWifiStatus.setText("WiFi on");
+ mWifiStatus.setBackgroundDrawable(getResources().getDrawable(R.drawable.wifi_on));
+ }
+ else
+ {
+ mWifiStatus.setText("WiFi off");
+ mWifiStatus.setBackgroundDrawable(getResources().getDrawable(R.drawable.wifi_off));
+ }
+
+ long fileBytes = 0;
+ File dir = new File(_pathFolder);
+ for (File child : dir.listFiles()) {
+ if (".".equals(child.getName()) || "..".equals(child.getName()))
+ continue;
+ try {
+ if (!child.getName().startsWith("incomplete"))
+ fileBytes = fileBytes + child.length();
+ }
+ catch (Exception e) {e.printStackTrace();}
+ }
+
+ if (fileBytes == 0) {
+ mUpload.setText("No Files");
+ mUpload.setEnabled(false);
+ }
+ else {
+ if (fileBytes < 1024 * 1024)
+ mUpload.setText("Upload " + (fileBytes / 1024) + "KB");
+ else
+ mUpload.setText("Upload " + (fileBytes / 1024 / 1024) + "MB");
+ mUpload.setEnabled(true);
+ }
+
+ Button mStatus = (Button) findViewById(R.id.textView3);
+ if (onlyWhenScreenOff())
+ mStatus.setText("Recording when screen is off");
+ else if (recordingStatus())
+ mStatus.setText("Recording Now");
+ else
+ mStatus.setText("Not Recording");
+ }
+ catch (Exception e) {e.printStackTrace();}
+ }
+
+ private void checkAppKeySetup() {
+ if (APP_KEY.startsWith("CHANGE") || APP_SECRET.startsWith("CHANGE")) {
+ finish();
+ return;
+ }
+
+ // Check if the app has set up its manifest properly.
+ Intent testIntent = new Intent(Intent.ACTION_VIEW);
+ String scheme = "db-" + APP_KEY;
+ String uri = scheme + "://" + AuthActivity.AUTH_VERSION + "/test";
+ testIntent.setData(Uri.parse(uri));
+ PackageManager pm = getPackageManager();
+ if (0 == pm.queryIntentActivities(testIntent, 0).size())
+ finish();
+ }
+
+ private String[] getKeys() {
+ SharedPreferences prefs = getSharedPreferences(ACCOUNT_PREFS_NAME, 0);
+ String key = prefs.getString(ACCESS_KEY_NAME, null);
+ String secret = prefs.getString(ACCESS_SECRET_NAME, null);
+ if (key != null && secret != null) {
+ String[] ret = new String[2];
+ ret[0] = key;
+ ret[1] = secret;
+ return ret;
+ } else {
+ return null;
+ }
+ }
+
+ private void storeKeys(String key, String secret) {
+ SharedPreferences prefs = getSharedPreferences(ACCOUNT_PREFS_NAME, 0);
+ Editor edit = prefs.edit();
+ edit.putString(ACCESS_KEY_NAME, key);
+ edit.putString(ACCESS_SECRET_NAME, secret);
+ edit.commit();
+ }
+
+ private void clearKeys() {
+ SharedPreferences prefs = getSharedPreferences(ACCOUNT_PREFS_NAME, 0);
+ Editor edit = prefs.edit();
+ edit.clear();
+ edit.commit();
+ }
+
+ private AndroidAuthSession buildSession() {
+ AppKeyPair appKeyPair = new AppKeyPair(APP_KEY, APP_SECRET);
+ AndroidAuthSession session;
+
+ String[] stored = getKeys();
+ if (stored != null) {
+ AccessTokenPair accessToken = new AccessTokenPair(stored[0],stored[1]);
+ session = new AndroidAuthSession(appKeyPair, ACCESS_TYPE,accessToken);
+ }
+ else
+ session = new AndroidAuthSession(appKeyPair, ACCESS_TYPE);
+
+ return session;
+ }
+
+ protected void uploaderStart() {
+ String key = null;
+ try {
+ SharedPreferences settings = getSharedPreferences("LifeBoxAppPreferences", 0);
+ key = settings.getString("key",null);
+ Upload upload = new Upload(this, mDBApi, _pathFolder, key);
+ upload.execute();
+ }
+ catch (Exception e) {e.printStackTrace();}
+
+ redraw_display();
+ }
+
+ private boolean recordingStatus() {
+ SharedPreferences settings = getSharedPreferences("LifeBoxAppPreferences", 0);
+ return settings.getBoolean("recordingStatus", false);
+ }
+
+ private boolean onlyWhenScreenOff() {
+ SharedPreferences settings = getSharedPreferences("LifeBoxAppPreferences", 0);
+ return settings.getBoolean("onlyWhenScreenOff", false);
+ }
+}
diff --git a/app/src/main/java/com/lifebox/lifeboxapp/RecordingService.java b/app/src/main/java/com/lifebox/lifeboxapp/RecordingService.java
new file mode 100644
index 0000000..9b81843
--- /dev/null
+++ b/app/src/main/java/com/lifebox/lifeboxapp/RecordingService.java
@@ -0,0 +1,254 @@
+package com.lifebox.lifeboxapp;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.media.MediaRecorder;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Handler;
+import android.os.IBinder;
+import android.support.v4.content.LocalBroadcastManager;
+
+public class RecordingService extends Service {
+ public static final String REDRAW_MAIN = "com.lifebox.lifeboxapp.RecordingService.REDRAW_MAIN";
+ public static final String RECORDING_STOP = "com.lifebox.lifeboxapp.RecordingService.RECORDING_STOP";
+ public static final String RECORDING_START = "com.lifebox.lifeboxapp.RecordingService.RECORDING_START";
+ public static final String SCREENOFF_ON = "com.lifebox.lifeboxapp.RecordingService.SCREENOFF_ON";
+ public static final String SCREENOFF_OFF = "com.lifebox.lifeboxapp.RecordingService.SCREENOFF_OFF";
+
+ private DataUpdateReceiver dataUpdateReceiver;
+ private GlobalReceiver globalReceiver;
+ private String folderPath = "";
+ private static MediaRecorder recorder = null;
+ protected ConnectivityManager connManager;
+ protected NetworkInfo mWifi;
+ protected String filename;
+ private static String InstallationId = "";
+ private boolean notificationIconFlag = false;
+
+ private static final Object lock = new Object();
+
+ private class DataUpdateReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(RecordingService.RECORDING_START)) {
+ recordingStatus(true);
+ loop();
+ }
+ if (intent.getAction().equals(RecordingService.RECORDING_STOP)) {
+ recordingStatus(false);
+ stop();
+ }
+ if (intent.getAction().equals(RecordingService.SCREENOFF_ON))
+ notificationIconBoolean(true);
+ if (intent.getAction().equals(RecordingService.SCREENOFF_OFF))
+ notificationIconBoolean(false);
+ }
+ }
+
+ private class GlobalReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
+ if (onlyWhenScreenOff()) {
+ recordingStatus(false);
+ stop();
+ }
+ }
+
+ if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+ if (onlyWhenScreenOff()) {
+ recordingStatus(true);
+ loop();
+ }
+ }
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ recordingStatus(false);
+
+ if (dataUpdateReceiver == null)
+ dataUpdateReceiver = new DataUpdateReceiver();
+ IntentFilter intentFilter = new IntentFilter(RecordingService.RECORDING_START);
+ intentFilter.addAction(RecordingService.RECORDING_STOP);
+ intentFilter.addAction(RecordingService.SCREENOFF_ON);
+ intentFilter.addAction(RecordingService.SCREENOFF_OFF);
+ LocalBroadcastManager.getInstance(this).registerReceiver(dataUpdateReceiver, intentFilter);
+
+ if (globalReceiver == null)
+ globalReceiver = new GlobalReceiver();
+ IntentFilter intentFilter2 = new IntentFilter(Intent.ACTION_SCREEN_ON);
+ intentFilter2.addAction(Intent.ACTION_SCREEN_OFF);
+ registerReceiver(globalReceiver, intentFilter2);
+
+ folderPath = getExternalFilesDir(null).toString();
+
+ connManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
+
+ if (onlyWhenScreenOff())
+ notificationIconBoolean(true);
+ }
+
+ @Override
+ public void onDestroy() {
+ if (dataUpdateReceiver != null)
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(dataUpdateReceiver);
+ recordingStatus(false);
+ stop();
+ stopForeground(true);
+ }
+
+ protected void loop() {
+ if (recordingStatus())
+ {
+ start();
+ LocalBroadcastManager.getInstance(this).sendBroadcast(
+ new Intent(RecordingService.REDRAW_MAIN)
+ );
+ Handler handler = new Handler();
+ handler.postDelayed(new Runnable() {
+ public void run() {
+ stop();
+ loop();
+ }
+ }, 1800000); // Split files every 30 minutes
+ //}, 180000); // Split files every 3 minutes for debugging
+ }
+ }
+
+ private void stop() {
+ System.out.println("Stop");
+ synchronized(lock) {
+ try {
+ if (recorder != null) {
+ recorder.stop();
+ recorder.reset();
+ recorder.release();
+ recorder = null;
+ }
+ File f = new File(folderPath + "/" + "incomplete_" + filename);
+ if (f.exists())
+ f.renameTo(new File(folderPath + "/" + filename));
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ recorder = null;
+ }
+ }
+ }
+
+ private void start() {
+ System.out.println("Start");
+ if (recordingStatus()) {
+ synchronized(lock) {
+ try {
+ recorder = new MediaRecorder();
+ //filename = getFileName() + ".3gp";
+ //recorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+ //recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+ //recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
+ filename = getFileName() + ".mp4";
+ recorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+ recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
+ recorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC);
+ recorder.setAudioChannels(1);
+ recorder.setOutputFile(folderPath + "/" + "incomplete_" + filename);
+
+ recorder.prepare();
+ recorder.start();
+ }
+ catch (Exception e) {e.printStackTrace();}
+ }
+ }
+ }
+
+ protected String getFileName() {
+ return InstallationId + "_" + getUtcTime();
+ }
+
+ public static String getUtcTime()
+ {
+ final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
+ sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+ return sdf.format(new Date());
+ }
+
+ private void recordingStatus(boolean value) {
+ SharedPreferences settings = getSharedPreferences("LifeBoxAppPreferences", 0);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean("recordingStatus", value);
+ editor.commit();
+ notificationIconBoolean(value);
+ }
+
+ private void notificationIconBoolean(boolean toggle)
+ {
+ if (toggle && !notificationIconFlag)
+ {
+ // This puts the icon in the notification area and gives lifebox the
+ // 'foreground' status so it cannot be killed by android to save memory.
+ int icon = R.drawable.icon;
+ CharSequence tickerText = "";
+ long when = System.currentTimeMillis();
+ Notification notification = new Notification(icon, tickerText, when);
+ Context context = getApplicationContext();
+ InstallationId = Installation.id(context);
+
+ Intent intent = new Intent("android.intent.action.MAIN");
+ intent.setComponent(ComponentName.unflattenFromString(
+ "com.lifebox.lifeboxapp/com.lifebox.lifeboxapp.LifeBoxAppMain"));
+ intent.addCategory("android.intent.category.LAUNCHER");
+ PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, 0);
+
+ if (onlyWhenScreenOff())
+ notification.setLatestEventInfo(context, "Lifebox",
+ "Waiting to record when screen is off", contentIntent);
+ else if (recordingStatus())
+ notification.setLatestEventInfo(context, "Lifebox",
+ "Recording", contentIntent);
+ else
+ notification.setLatestEventInfo(context, "Lifebox",
+ "Unknown status", contentIntent);
+
+ startForeground(1337, notification);
+ notificationIconFlag = true;
+ }
+ else if (!onlyWhenScreenOff())
+ {
+ // This removes the icon from the notification area and ends the 'foreground' status.
+ stopForeground(true);
+ notificationIconFlag = false;
+ }
+ }
+
+ private boolean recordingStatus() {
+ SharedPreferences settings = getSharedPreferences("LifeBoxAppPreferences", 0);
+ return settings.getBoolean("recordingStatus", false);
+ }
+
+ private boolean onlyWhenScreenOff() {
+ SharedPreferences settings = getSharedPreferences("LifeBoxAppPreferences", 0);
+ return settings.getBoolean("onlyWhenScreenOff", false);
+ }
+}
diff --git a/app/src/main/java/com/lifebox/lifeboxapp/RecordingServiceBroadcastReceiver.java b/app/src/main/java/com/lifebox/lifeboxapp/RecordingServiceBroadcastReceiver.java
new file mode 100644
index 0000000..20b6561
--- /dev/null
+++ b/app/src/main/java/com/lifebox/lifeboxapp/RecordingServiceBroadcastReceiver.java
@@ -0,0 +1,15 @@
+package com.lifebox.lifeboxapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class RecordingServiceBroadcastReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
+ context.startService(new Intent(context, RecordingService.class));
+ }
+ }
+}
diff --git a/app/src/main/java/com/lifebox/lifeboxapp/Settings.java b/app/src/main/java/com/lifebox/lifeboxapp/Settings.java
new file mode 100644
index 0000000..8cf8c6a
--- /dev/null
+++ b/app/src/main/java/com/lifebox/lifeboxapp/Settings.java
@@ -0,0 +1,127 @@
+package com.lifebox.lifeboxapp;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.support.v4.content.LocalBroadcastManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.View.OnClickListener;
+import android.widget.CheckBox;
+import android.widget.EditText;
+
+public class Settings extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ this.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.settings);
+
+ final Context context = this;
+ final CheckBox ScreenOffCheckBox = (CheckBox) findViewById(R.id.checkBoxOnlyWhenScreenOff);
+ final CheckBox EncryptionCheckBox = (CheckBox) findViewById(R.id.checkBoxEncryption);
+ final SharedPreferences settings = getSharedPreferences("LifeBoxAppPreferences", 0);
+
+ if (settings.getBoolean("onlyWhenScreenOff",false))
+ ScreenOffCheckBox.setChecked(true);
+ else
+ ScreenOffCheckBox.setChecked(false);
+
+ String key = settings.getString("key",null);
+ if (key == null)
+ EncryptionCheckBox.setChecked(false);
+ else
+ EncryptionCheckBox.setChecked(true);
+
+ ScreenOffCheckBox.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ SharedPreferences.Editor editor = settings.edit();
+
+ if (settings.getBoolean("onlyWhenScreenOff",false)) {
+ editor.putBoolean("onlyWhenScreenOff", false);
+ editor.commit();
+ LocalBroadcastManager.getInstance(context).sendBroadcast(
+ new Intent(RecordingService.SCREENOFF_OFF));
+ }
+ else {
+ editor.putBoolean("onlyWhenScreenOff", true);
+ editor.commit();
+ LocalBroadcastManager.getInstance(context).sendBroadcast(
+ new Intent(RecordingService.SCREENOFF_ON));
+ }
+ }
+ });
+
+ EncryptionCheckBox.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ SharedPreferences.Editor editor = settings.edit();
+
+ if (settings.getString("key",null) == null) {
+ LayoutInflater li = LayoutInflater.from(context);
+ View promptsView = li.inflate(R.layout.password, null);
+ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context);
+ alertDialogBuilder.setView(promptsView);
+
+ final EditText userInput = (EditText) promptsView.findViewById(
+ R.id.editTextDialogUserInput);
+
+ // set dialog message
+ alertDialogBuilder.setCancelable(false)
+ .setPositiveButton("OK",
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,int id) {
+ String password = userInput.getText().toString();
+ if (!password.isEmpty()) {
+ setPassword(password, Installation.id(context));
+ EncryptionCheckBox.setChecked(true);
+ }
+ else
+ EncryptionCheckBox.setChecked(false);
+ }
+ })
+ .setNegativeButton("Cancel",
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog,int id) {
+ dialog.cancel();
+ EncryptionCheckBox.setChecked(false);
+ }
+ });
+
+ AlertDialog alertDialog = alertDialogBuilder.create();
+ alertDialog.show();
+ }
+ else {
+ deletePassword();
+ EncryptionCheckBox.setChecked(false);
+ }
+ }
+ });
+ }
+
+ private void setPassword(String password, String installationId) {
+ try {
+ String key = Crypto.makeKey(password,installationId);
+ SharedPreferences settings = getSharedPreferences("LifeBoxAppPreferences", 0);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString("key",key);
+ editor.commit();
+ }
+ catch (Exception e) {e.printStackTrace();}
+ }
+
+ private void deletePassword() {
+ try {
+ SharedPreferences settings = getSharedPreferences("LifeBoxAppPreferences", 0);
+ SharedPreferences.Editor editor = settings.edit();
+ editor.remove("key");
+ editor.commit();
+ }
+ catch (Exception e) {e.printStackTrace();}
+ }
+}
diff --git a/app/src/main/java/com/lifebox/lifeboxapp/Upload.java b/app/src/main/java/com/lifebox/lifeboxapp/Upload.java
new file mode 100644
index 0000000..26fa2b5
--- /dev/null
+++ b/app/src/main/java/com/lifebox/lifeboxapp/Upload.java
@@ -0,0 +1,209 @@
+/*
+This all needs to be replaced because the Dropbox API v1 is
+deprecated and will stop working in June 2017!
+https://blogs.dropbox.com/developers/2016/06/api-v1-deprecated/
+*/
+
+package com.lifebox.lifeboxapp;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.support.v4.content.LocalBroadcastManager;
+import android.widget.Toast;
+
+import com.dropbox.client2.DropboxAPI;
+import com.dropbox.client2.DropboxAPI.UploadRequest;
+import com.dropbox.client2.ProgressListener;
+import com.dropbox.client2.exception.DropboxException;
+import com.dropbox.client2.exception.DropboxFileSizeException;
+import com.dropbox.client2.exception.DropboxIOException;
+import com.dropbox.client2.exception.DropboxLocalStorageFullException;
+import com.dropbox.client2.exception.DropboxParseException;
+import com.dropbox.client2.exception.DropboxPartialFileException;
+import com.dropbox.client2.exception.DropboxServerException;
+import com.dropbox.client2.exception.DropboxUnlinkedException;
+
+public class Upload extends AsyncTask {
+ private DropboxAPI> dropboxApi;
+ private File fileDirectory;
+ private UploadRequest uploadRequest;
+ private Context context;
+ private final ProgressDialog progressDialog;
+ private int fileCount;
+ private String fileName;
+ private String errorMessage;
+ private String key = null;
+
+ public Upload(Context context, DropboxAPI> api, String directory, String encryptionKey) {
+ this.context = context.getApplicationContext();
+ key = encryptionKey;
+ dropboxApi = api;
+ fileDirectory = new File(directory);
+ progressDialog = new ProgressDialog(context);
+ progressDialog.setMax(0);
+ for (File child : fileDirectory.listFiles()) {
+ if (".".equals(child.getName()) || "..".equals(child.getName()))
+ continue;
+ try {
+ if (!child.getName().startsWith("incomplete")) {
+ progressDialog.setMax(progressDialog.getMax() + 1);
+ }
+ }
+ catch (Exception e) {e.printStackTrace();}
+ }
+ progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ fileName = "...";
+ progressDialog.setMessage("Uploading " + fileName);
+ fileCount = 0;
+ progressDialog.setProgress(fileCount);
+ progressDialog.setCanceledOnTouchOutside(false);
+ progressDialog.setCancelable(false);
+ progressDialog.show();
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ for (File file : fileDirectory.listFiles()) {
+ if (".".equals(file.getName()) || "..".equals(file.getName()))
+ continue;
+ try {
+ if (!file.getName().startsWith("incomplete")) {
+ fileName = file.getName();
+ FileInputStream inputStream = new FileInputStream(file);
+ try {
+ if (key != null) {
+ fileName = fileName+".enc";
+ int fileLength = inputStream.available();
+ byte[] fileBytes = new byte[fileLength];
+ inputStream.read(fileBytes);
+ Crypto crypto = new Crypto(key);
+ byte[] encrypted = crypto.encrypt(fileBytes);
+ InputStream encryptedStream = new ByteArrayInputStream(encrypted);
+ System.out.println("Uploading "+fileName);
+ uploadRequest = dropboxApi.putFileOverwriteRequest(fileName, encryptedStream,
+ encryptedStream.available(), new ProgressListener() {
+ @Override
+ public long progressInterval() {
+ return 500;
+ }
+
+ @Override
+ public void onProgress(long bytes, long total) {
+ publishProgress(bytes);
+ }
+ });
+ }
+ else {
+ uploadRequest = dropboxApi.putFileOverwriteRequest(fileName, inputStream,
+ file.length(), new ProgressListener() {
+ @Override
+ public long progressInterval() {
+ return 500;
+ }
+
+ @Override
+ public void onProgress(long bytes,
+ long total) {
+ publishProgress(bytes);
+ }
+ });
+ }
+ if (uploadRequest != null) {
+ uploadRequest.upload();
+ file.delete();
+ fileCount = fileCount + 1;
+ }
+
+ }
+ catch (DropboxUnlinkedException e) {
+ errorMessage = "Please sign in to Dropbox first.";
+ return false;
+ }
+ catch (DropboxFileSizeException e) {
+ errorMessage = "This file is too big to upload";
+ return false;
+ }
+ catch (DropboxPartialFileException e) {
+ errorMessage = "Upload canceled";
+ return false;
+ }
+ catch (DropboxServerException e) {
+ if (e.error == DropboxServerException._401_UNAUTHORIZED) {
+ errorMessage = "401 Unauthorized";
+ return false;
+ }
+ else if (e.error == DropboxServerException._403_FORBIDDEN) {
+ errorMessage = "403 Forbidden";
+ return false;
+ }
+ else if (e.error == DropboxServerException._404_NOT_FOUND) {
+ errorMessage = "404 Not Found";
+ return false;
+ }
+ else if (e.error == DropboxServerException._507_INSUFFICIENT_STORAGE) {
+ errorMessage = "Your Dropbox is full";
+ return false;
+ }
+ else {
+ errorMessage = e.body.userError;
+ if (errorMessage == null)
+ errorMessage = e.body.error;
+ return false;
+ }
+ }
+ catch (DropboxIOException e) {
+ errorMessage = "Network error. Try again.";
+ return false;
+ }
+ catch (DropboxParseException e) {
+ errorMessage = "Dropbox error. Try again.";
+ return false;
+ }
+ catch (DropboxLocalStorageFullException e) {
+ errorMessage = "Your Dropbox is full.";
+ return false;
+ }
+ catch (DropboxException e) {
+ errorMessage = "Unknown error. Try again.";
+ return false;
+ }
+ }
+ }
+ catch (Exception e) {
+ errorMessage = e.getMessage();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ protected void onProgressUpdate(Long... progress) {
+ progressDialog.setMessage("Uploading " + fileName);
+ progressDialog.setProgress(fileCount);
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ progressDialog.dismiss();
+ LocalBroadcastManager.getInstance(context).sendBroadcast(
+ new Intent(RecordingService.REDRAW_MAIN));
+ if (!result) {
+ showToast(errorMessage);
+ }
+ }
+
+ private void showToast(String msg) {
+ Toast error = Toast.makeText(context, msg, Toast.LENGTH_LONG);
+ error.show();
+ }
+}
+
+
diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-ldpi/ic_launcher.png b/app/src/main/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9923872
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-mdpi/background.jpg b/app/src/main/res/drawable-mdpi/background.jpg
new file mode 100644
index 0000000..55e663f
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/background.jpg differ
diff --git a/app/src/main/res/drawable-mdpi/blank.9.png b/app/src/main/res/drawable-mdpi/blank.9.png
new file mode 100644
index 0000000..1fbda87
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/blank.9.png differ
diff --git a/app/src/main/res/drawable-mdpi/dropbox.9.png b/app/src/main/res/drawable-mdpi/dropbox.9.png
new file mode 100644
index 0000000..eb73205
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/dropbox.9.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..58fe4ac
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/drawable-mdpi/icon.png b/app/src/main/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..e8fcd98
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/icon.png differ
diff --git a/app/src/main/res/drawable-mdpi/icon_big.jpg b/app/src/main/res/drawable-mdpi/icon_big.jpg
new file mode 100644
index 0000000..feb8a0f
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/icon_big.jpg differ
diff --git a/app/src/main/res/drawable-mdpi/logo.png b/app/src/main/res/drawable-mdpi/logo.png
new file mode 100644
index 0000000..08ed7f9
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/logo.png differ
diff --git a/app/src/main/res/drawable-mdpi/logo3.png b/app/src/main/res/drawable-mdpi/logo3.png
new file mode 100644
index 0000000..c55f4e7
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/logo3.png differ
diff --git a/app/src/main/res/drawable-mdpi/recording_off.9.png b/app/src/main/res/drawable-mdpi/recording_off.9.png
new file mode 100644
index 0000000..9f7d18f
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/recording_off.9.png differ
diff --git a/app/src/main/res/drawable-mdpi/recording_on.9.png b/app/src/main/res/drawable-mdpi/recording_on.9.png
new file mode 100644
index 0000000..fa3785b
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/recording_on.9.png differ
diff --git a/app/src/main/res/drawable-mdpi/settings.9.png b/app/src/main/res/drawable-mdpi/settings.9.png
new file mode 100644
index 0000000..0ba3022
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/settings.9.png differ
diff --git a/app/src/main/res/drawable-mdpi/upload.9.png b/app/src/main/res/drawable-mdpi/upload.9.png
new file mode 100644
index 0000000..15c6beb
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/upload.9.png differ
diff --git a/app/src/main/res/drawable-mdpi/wifi_off.9.png b/app/src/main/res/drawable-mdpi/wifi_off.9.png
new file mode 100644
index 0000000..946465c
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/wifi_off.9.png differ
diff --git a/app/src/main/res/drawable-mdpi/wifi_on.9.png b/app/src/main/res/drawable-mdpi/wifi_on.9.png
new file mode 100644
index 0000000..ede174c
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/wifi_on.9.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml
new file mode 100644
index 0000000..ff263a5
--- /dev/null
+++ b/app/src/main/res/layout/main.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/password.xml b/app/src/main/res/layout/password.xml
new file mode 100644
index 0000000..4f3b8a5
--- /dev/null
+++ b/app/src/main/res/layout/password.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/settings.xml b/app/src/main/res/layout/settings.xml
new file mode 100644
index 0000000..56bfbbb
--- /dev/null
+++ b/app/src/main/res/layout/settings.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..045e125
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..cf36b0e
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,18 @@
+
+
+
+ LifeBoxApp
+
+
+- 10 Minutes
+
+
+
+- Default
+
+
+
+- Dropbox
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..bbe9eb2
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,15 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:2.1.3'
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..d143998
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Aug 29 21:47:25 AEST 2016
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..9d82f78
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app'