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 @@ + + + + + +