6 Commits

Author SHA1 Message Date
joel 9171ebf63e add prompt 2021-07-05 19:58:34 +02:00
joel 76bdb4b107 add some strings 2021-07-05 19:57:50 +02:00
joel c488052501 add FileChooser 2021-06-13 21:39:29 +02:00
joel 06e3d7972f add menu 2021-06-13 21:39:20 +02:00
joel 5ec72eddd4 update config 2021-06-13 21:38:51 +02:00
joel c37f51bff8 Preparations for backup feature 2021-06-13 21:38:39 +02:00
16 changed files with 444 additions and 10 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" /> <bytecodeTargetLevel target="11" />
</component> </component>
</project> </project>
+8
View File
@@ -0,0 +1,8 @@
<component name="ProjectDictionaryState">
<dictionary name="Joel">
<words>
<w>videomeetings</w>
<w>zoomhelper</w>
</words>
</dictionary>
</component>
+2 -2
View File
@@ -4,10 +4,11 @@
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="delegatedBuild" value="true" />
<option name="testRunner" value="PLATFORM" /> <option name="testRunner" value="PLATFORM" />
<option name="disableWrapperSourceDistributionNotification" value="true" />
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="14" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
@@ -15,7 +16,6 @@
</set> </set>
</option> </option>
<option name="resolveModulePerSourceSet" value="false" /> <option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="Android API 30 Platform" project-jdk-type="Android SDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
</set>
</option>
</component>
</project>
+1 -1
View File
@@ -36,7 +36,7 @@ repositories {
dependencies { dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.lifecycle:lifecycle-process:2.3.1' implementation 'androidx.lifecycle:lifecycle-process:2.3.1'
+1 -1
View File
@@ -11,7 +11,7 @@
<application <application
android:allowBackup="true" android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
@@ -0,0 +1,143 @@
package de.joel.zoomhelper;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
public class BackupController {
@RequiresApi(api = Build.VERSION_CODES.O)
public void backup(Activity activity) {
Encrypter encrypter = new Encrypter();
SharedPreferences mPrefs = activity.getPreferences(Context.MODE_PRIVATE);
String value = mPrefs.getString("Meetings", "");
final File tempDestination = new File(activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(), "backup.tmp");
final File destination = new File(activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(), "backup.zoomhelper");
String password;
try {
writeStringToFile(value, tempDestination.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
// get prompts.xml view
LayoutInflater li = LayoutInflater.from(activity);
View promptsView = li.inflate(R.layout.prompts, null);
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(activity);
// set prompts.xml to alertdialog builder
alertDialogBuilder.setView(promptsView);
final EditText userInput = (EditText) promptsView
.findViewById(R.id.editTextDialogUserInput);
// set dialog message
alertDialogBuilder
.setCancelable(false)
.setPositiveButton("OK",
(dialog, id) -> {
try {
encrypter.encrypt(tempDestination.getAbsolutePath(), userInput.getText().toString());
} catch (BadPaddingException | IllegalBlockSizeException | IOException | NoSuchAlgorithmException | InvalidKeySpecException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchPaddingException e) {
e.printStackTrace();
}
//noinspection ResultOfMethodCallIgnored
tempDestination.delete();
shareFile(destination, activity);
})
.setNegativeButton("Cancel",
(dialog, id) -> dialog.cancel());
// create alert dialog
AlertDialog alertDialog = alertDialogBuilder.create();
// show it
alertDialog.show();
}
//TODO:Methode erneut aufrufen dann mit Passwort, mit vorheriger Quelle
public void restore(Activity activity) {
new FileChooser(activity).openFile();
}
@SuppressLint("ApplySharedPref")
public void restore(Activity activity, InputStream inp) {
Encrypter encrypter = new Encrypter();
SharedPreferences mPrefs = activity.getPreferences(Context.MODE_PRIVATE);
String decrypted = null;
//TODO: Passwort einlesen aus Eingabefeld
try {
decrypted = encrypter.decrypt(inp, "test");
} catch (IOException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeySpecException | IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
}
//TODO: Hinweis, dass Daten mit den Daten aus dem Backup überschrieben werden...
SharedPreferences.Editor prefsEditor = mPrefs.edit();
prefsEditor.putString("Meetings",decrypted);
prefsEditor.commit();
prefsEditor.putString("lastMeeting", "0");
//TODO: Fehlermeldung bei falschem Passwort
}
public void writeStringToFile(String str, String fileName)
throws IOException {
FileOutputStream outputStream = new FileOutputStream(fileName);
byte[] strToBytes = str.getBytes();
outputStream.write(strToBytes);
outputStream.close();
}
public void shareFile(File file, Activity activity){
Intent intentShareFile = new Intent(Intent.ACTION_SEND);
if(file.exists()) {
intentShareFile.setType("application/octet-stream");
intentShareFile.putExtra(Intent.EXTRA_STREAM, Uri.parse(file.getAbsolutePath()));
intentShareFile.putExtra(Intent.EXTRA_SUBJECT,
"Sharing File...");
intentShareFile.putExtra(Intent.EXTRA_TEXT, "Sharing File...");
activity.startActivity(Intent.createChooser(intentShareFile, "Share File"));
}
}
}
@@ -0,0 +1,154 @@
package de.joel.zoomhelper;
import android.os.Build;
import android.os.Environment;
import androidx.annotation.RequiresApi;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.spec.KeySpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.IvParameterSpec;
public class Encrypter {
@RequiresApi(api = Build.VERSION_CODES.O)
public void encrypt(String inputFile, String password) throws BadPaddingException, IllegalBlockSizeException, IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException {
SecureRandom srandom = null;
try {
srandom = SecureRandom.getInstanceStrong();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
byte[] salt = new byte[8];
assert srandom != null;
srandom.nextBytes(salt);
SecretKeyFactory factory =
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10000, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec skey = new SecretKeySpec(tmp.getEncoded(), "AES");
byte[] iv = new byte[128 / 8];
srandom.nextBytes(iv);
IvParameterSpec ivspec = new IvParameterSpec(iv);
FileOutputStream out = null;
out = new FileOutputStream(inputFile + ".enc");
out.write(salt);
out.write(iv);
Cipher ci = null;
ci = Cipher.getInstance("AES/CBC/PKCS5Padding");
ci.init(Cipher.ENCRYPT_MODE, skey, ivspec);
try (FileInputStream in = new FileInputStream(inputFile)) {
processFile(ci, in, out);
}
}
static private void processFile(Cipher ci, InputStream in, OutputStream out)
throws javax.crypto.IllegalBlockSizeException,
javax.crypto.BadPaddingException,
java.io.IOException {
byte[] ibuf = new byte[1024];
int len;
while ((len = in.read(ibuf)) != -1) {
byte[] obuf = ci.update(ibuf, 0, len);
if (obuf != null) out.write(obuf);
}
byte[] obuf = ci.doFinal();
if (obuf != null) out.write(obuf);
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public String decrypt(String inputFile, String password) throws IOException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException {
FileInputStream in = new FileInputStream(inputFile);
byte[] salt = new byte[8], iv = new byte[128 / 8];
in.read(salt);
in.read(iv);
SecretKeyFactory factory =
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10000, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec skey = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher ci = Cipher.getInstance("AES/CBC/PKCS5Padding");
ci.init(Cipher.DECRYPT_MODE, skey, new IvParameterSpec(iv));
OutputStream out = new OutputStream() {
private final StringBuilder string = new StringBuilder();
@Override
public void write(int b) throws IOException {
this.string.append((char) b );
}
//Netbeans IDE automatically overrides this toString()
public String toString() {
return this.string.toString();
}
};
processFile(ci, in, out);
return out.toString();
}
public String decrypt(InputStream in, String password) throws IOException, InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException {
byte[] salt = new byte[8], iv = new byte[128 / 8];
in.read(salt);
in.read(iv);
SecretKeyFactory factory =
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10000, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec skey = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher ci = Cipher.getInstance("AES/CBC/PKCS5Padding");
ci.init(Cipher.DECRYPT_MODE, skey, new IvParameterSpec(iv));
OutputStream out = new OutputStream() {
private final StringBuilder string = new StringBuilder();
@Override
public void write(int b) throws IOException {
this.string.append((char) b );
}
//Netbeans IDE automatically overrides this toString()
public String toString() {
return this.string.toString();
}
};
processFile(ci, in, out);
return out.toString();
}
}
@@ -0,0 +1,22 @@
package de.joel.zoomhelper;
import android.app.Activity;
import android.content.Intent;
import java.io.File;
public class FileChooser {
// Request code for selecting a PDF document.
private final Activity act;
public FileChooser (Activity act) {
this.act = act;
}
public void openFile() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/octet-stream");
act.startActivityForResult(intent, 2);
}
}
@@ -5,7 +5,11 @@ import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.AutoCompleteTextView; import android.widget.AutoCompleteTextView;
import android.widget.EditText; import android.widget.EditText;
@@ -14,6 +18,7 @@ import android.widget.LinearLayout;
import android.widget.ScrollView; import android.widget.ScrollView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.pm.ShortcutInfoCompat; import androidx.core.content.pm.ShortcutInfoCompat;
@@ -25,6 +30,8 @@ import com.github.javiersantos.appupdater.enums.UpdateFrom;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -140,6 +147,56 @@ public class MainActivity extends AppCompatActivity {
} }
} }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public boolean onOptionsItemSelected(MenuItem item) {
final int item_backup = R.id.menu_backup;
final int item_restore = R.id.menu_restore;
final int item_settings = R.id.menu_settings;
final int item_info = R.id.menu_info;
final BackupController backupController = new BackupController();
switch (item.getItemId()) {
case item_backup:
backupController.backup(this);
break;
case item_restore:
backupController.restore(this);
break;
default:
break;
}
return true;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Uri fileUri = null;
String filePath = null;
if (requestCode == 2) {
fileUri = data.getData();
filePath = fileUri.getPath();
}
try {
new BackupController().restore(this, getContentResolver().openInputStream(fileUri));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// new BackupController().restore(this, file.getAbsolutePath());
}
private void importFromClipboard() { private void importFromClipboard() {
final LinearLayout layoutBegin = findViewById(R.id.layoutBegin); final LinearLayout layoutBegin = findViewById(R.id.layoutBegin);
final ClipboardManager clipboard = (ClipboardManager) getApplicationContext().getSystemService(CLIPBOARD_SERVICE); final ClipboardManager clipboard = (ClipboardManager) getApplicationContext().getSystemService(CLIPBOARD_SERVICE);
@@ -154,7 +211,7 @@ public class MainActivity extends AppCompatActivity {
if (clipboard.hasPrimaryClip() && clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN)) { if (clipboard.hasPrimaryClip() && clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN)) {
final String text = clipboard.getPrimaryClip().getItemAt(0).getText().toString(); final String text = clipboard.getPrimaryClip().getItemAt(0).getText().toString();
if ( meetingImportController.handleSendText(text, true)) { if (meetingImportController.handleSendText(text, true)) {
snackbar.show(); snackbar.show();
} }
@@ -119,6 +119,7 @@ public class MeetingsController {
return meetingList; return meetingList;
} }
//TODO: Nach Remove bleibt Meeting noch stehen
public void removeMeeting(int meeting) { public void removeMeeting(int meeting) {
MainActivity act = (MainActivity)context; MainActivity act = (MainActivity)context;
ArrayList<Meeting> meetings = getMeetings(); ArrayList<Meeting> meetings = getMeetings();
+1 -2
View File
@@ -11,8 +11,7 @@
android:layout_marginTop="22dp" android:layout_marginTop="22dp"
android:onClick="btnSave_onClick" android:onClick="btnSave_onClick"
android:text="@string/Save" android:text="@string/Save"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintTop_toBottomOf="@+id/imageTrash" />
<Button <Button
android:id="@+id/btnJoin" android:id="@+id/btnJoin"
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_root"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:padding="10dp" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/password_input"
android:textAppearance="?android:attr/textAppearanceLarge" />
<EditText
android:id="@+id/editTextDialogUserInput"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<requestFocus />
</EditText>
</LinearLayout>
+10
View File
@@ -0,0 +1,10 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/menu_backup"
android:title="@string/backup" />
<item android:id="@+id/menu_restore"
android:title="@string/restore" />
<item android:id="@+id/menu_settings"
android:title="@string/settings" />
<item android:id="@+id/menu_info"
android:title="@string/info" />
</menu>
+5
View File
@@ -41,5 +41,10 @@
<string name="cancel">abbrechen</string> <string name="cancel">abbrechen</string>
<string name="meetingSaved">Meeting gespeichert</string> <string name="meetingSaved">Meeting gespeichert</string>
<string name="meetingWithoutID">Meeting kann nicht ohne ID gespeichert werden!</string> <string name="meetingWithoutID">Meeting kann nicht ohne ID gespeichert werden!</string>
<string name="backup">Daten sichern</string>
<string name="restore">Daten wiederherstellen</string>
<string name="settings">Einstellungen</string>
<string name="info">Info</string>
<string name="password_input">"Passworteingabe: "</string>
</resources> </resources>