Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c488052501 | |||
| 06e3d7972f | |||
| 5ec72eddd4 | |||
| c37f51bff8 | |||
| 75b67ea2f0 | |||
| 355f7a7758 | |||
| 42a2c353c6 | |||
| a67ed4390b | |||
| f539bc3184 | |||
| 6eef26e116 | |||
| 719224003e | |||
| 7cce6f1280 | |||
| fe99d0a377 | |||
| 8dbd025c36 | |||
| c240223e98 | |||
| 1c68db62b1 | |||
| 1a8377b558 | |||
| 6dbcb548b5 | |||
| 066e3db80e | |||
| 50be09f6b4 |
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="1.8" />
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="Joel">
|
||||
<words>
|
||||
<w>videomeetings</w>
|
||||
<w>zoomhelper</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
||||
Generated
+2
-3
@@ -4,11 +4,11 @@
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="delegatedBuild" value="true" />
|
||||
<option name="testRunner" value="PLATFORM" />
|
||||
<option name="disableWrapperSourceDistributionNotification" value="true" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="11" />
|
||||
<option name="gradleJvm" value="14" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
@@ -16,7 +16,6 @@
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
<option name="useQualifiedModuleNames" value="true" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="ConstantConditions" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="SUGGEST_NULLABLE_ANNOTATIONS" value="false" />
|
||||
<option name="DONT_REPORT_TRUE_ASSERT_STATEMENTS" value="false" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SerializableHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||
<option name="superClassString" value="java.awt.Component" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
Generated
+10
@@ -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>
|
||||
+4
-4
@@ -10,8 +10,8 @@ android {
|
||||
applicationId "de.joel.zoomhelper"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode 13
|
||||
versionName '0.4.9'
|
||||
versionCode 15
|
||||
versionName '0.5.1'
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -36,10 +36,10 @@ repositories {
|
||||
|
||||
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 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
implementation 'androidx.lifecycle:lifecycle-process:2.2.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-process:2.3.1'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package de.joel.zoomhelper;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.DownloadManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class APKDownloader {
|
||||
public void downloadAPK(Context context, String fileName, Uri uri) {
|
||||
Activity act = (Activity)context;
|
||||
File destination = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(), fileName);
|
||||
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
|
||||
if (!context.getPackageManager().canRequestPackageInstalls()) {
|
||||
Toast.makeText(context, R.string.PleaseAllowUnknownSources, Toast.LENGTH_LONG).show();
|
||||
act.startActivityForResult(new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:de.joel.zoomhelper")), 101);
|
||||
Timer timer = new Timer(true);
|
||||
timer.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (context.getPackageManager().canRequestPackageInstalls()) {
|
||||
act.finishActivity(101);
|
||||
downloadAPK(context, fileName, uri);
|
||||
timer.cancel();
|
||||
}
|
||||
}
|
||||
}, 0, 1000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Delete update file if exists
|
||||
final Uri localUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", destination);
|
||||
if (destination.exists()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
destination.delete();
|
||||
}
|
||||
|
||||
//Download Script
|
||||
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
DownloadManager.Request request = new DownloadManager.Request(uri);
|
||||
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
|
||||
Log.d("Desturi", localUri.getPath());
|
||||
request.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, fileName);
|
||||
final long downloadID = downloadManager.enqueue(request);
|
||||
|
||||
BroadcastReceiver onDownloadComplete = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
//Fetching the download id received with the broadcast
|
||||
long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
|
||||
//Checking if the received broadcast is for our enqueued download by matching download id
|
||||
if (downloadID == id) {
|
||||
Intent install = new Intent(Intent.ACTION_VIEW);
|
||||
install.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
install.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
|
||||
install.setDataAndType(localUri,
|
||||
downloadManager.getMimeTypeForDownloadedFile(downloadID));
|
||||
context.startActivity(install);
|
||||
|
||||
context.unregisterReceiver(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
context.registerReceiver(onDownloadComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package de.joel.zoomhelper;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class Attendee implements Serializable {
|
||||
private static final long serialVersionUID = 6196688086302483907L;
|
||||
private String name;
|
||||
private String num;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getNum() {
|
||||
return num;
|
||||
}
|
||||
|
||||
public void setNum(String num) {
|
||||
this.num = num;
|
||||
}
|
||||
|
||||
public Attendee(String name, String num) {
|
||||
this.name = name;
|
||||
this.num = num;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package de.joel.zoomhelper;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
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", "");
|
||||
File destination = new File(activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(), "backup.tmp");
|
||||
|
||||
try {
|
||||
writeStringToFile(value, destination.getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
//TODO: Eingabefeld für Passwort
|
||||
try {
|
||||
encrypter.encrypt(destination.getAbsolutePath(), "test");
|
||||
} catch (BadPaddingException | IllegalBlockSizeException | IOException | NoSuchAlgorithmException | InvalidKeySpecException | InvalidAlgorithmParameterException | InvalidKeyException | NoSuchPaddingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
destination.delete();
|
||||
destination = new File(activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(), "backup.zoomhelper");
|
||||
|
||||
shareFile(destination, activity);
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package de.joel.zoomhelper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class InstantAutoComplete extends androidx.appcompat.widget.AppCompatAutoCompleteTextView {
|
||||
|
||||
public InstantAutoComplete(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public InstantAutoComplete(Context arg0, AttributeSet arg1) {
|
||||
super(arg0, arg1);
|
||||
}
|
||||
|
||||
public InstantAutoComplete(Context arg0, AttributeSet arg1, int arg2) {
|
||||
super(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enoughToFilter() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,109 @@
|
||||
package de.joel.zoomhelper;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class Meeting implements Serializable {
|
||||
private static final long serialVersionUID = 2606722401897866931L;
|
||||
private String meetingName;
|
||||
private String meetingID;
|
||||
private String meetingPWD;
|
||||
private ArrayList<Attendee> attendees;
|
||||
private int lastAtt;
|
||||
|
||||
public String getMeetingName() {
|
||||
return meetingName;
|
||||
}
|
||||
|
||||
public void setMeetingName(String meetingName) {
|
||||
this.meetingName = meetingName;
|
||||
}
|
||||
|
||||
public String getMeetingID() {
|
||||
return meetingID;
|
||||
}
|
||||
|
||||
public void setMeetingID(String meetingID) {
|
||||
this.meetingID = meetingID;
|
||||
}
|
||||
|
||||
public String getMeetingPWD() {
|
||||
return meetingPWD;
|
||||
}
|
||||
|
||||
public void setMeetingPWD(String meetingPWD) {
|
||||
this.meetingPWD = meetingPWD;
|
||||
}
|
||||
|
||||
public ArrayList<Attendee> getAttendees() {
|
||||
return attendees;
|
||||
}
|
||||
|
||||
public void setAttendees(ArrayList<Attendee> attendees) {
|
||||
this.attendees = attendees;
|
||||
}
|
||||
|
||||
public int getLastAtt() {
|
||||
return lastAtt;
|
||||
}
|
||||
|
||||
public void setLastAtt(int lastAtt) {
|
||||
this.lastAtt = lastAtt;
|
||||
}
|
||||
|
||||
public Meeting(String meetingName, String meetingID, String meetingPWD, String nameAttendee, String numAttendee) {
|
||||
this.meetingName = meetingName;
|
||||
this.meetingID = meetingID;
|
||||
this.meetingPWD = meetingPWD;
|
||||
this.attendees = new ArrayList<>();
|
||||
Attendee attendee = new Attendee(nameAttendee, numAttendee);
|
||||
this.attendees.add(attendee);
|
||||
this.lastAtt = 0;
|
||||
}
|
||||
|
||||
public void addAttendee(String name, String num) {
|
||||
Attendee attendee = new Attendee(name, num);
|
||||
this.attendees.add(attendee);
|
||||
this.lastAtt = attendees.size() - 1;
|
||||
}
|
||||
|
||||
public int searchAttendee(String name) {
|
||||
int found = -1;
|
||||
for (int i = 0; i < this.attendees.size(); i++) {
|
||||
if (this.attendees.get(i).getName().equals(name)) {
|
||||
found = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
public void updateAttendee(String name, String num) {
|
||||
|
||||
int found = searchAttendee(name);
|
||||
|
||||
if (found == -1) {
|
||||
addAttendee(name, num);
|
||||
} else {
|
||||
this.attendees.get(found).setNum(num);
|
||||
this.lastAtt = found;
|
||||
}
|
||||
}
|
||||
|
||||
public String info() {
|
||||
return "Meeting Name: " + this.meetingName + "\n" +
|
||||
"Meeting-ID: " + this.meetingID + "\n" +
|
||||
"Kenncode: " + this.meetingPWD;
|
||||
}
|
||||
|
||||
public Intent share() {
|
||||
final Intent sendIntent = new Intent();
|
||||
sendIntent.setAction(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, this.info());
|
||||
sendIntent.setType("text/plain");
|
||||
|
||||
return Intent.createChooser(sendIntent, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package de.joel.zoomhelper;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MeetingImportController {
|
||||
private final MeetingsController meetingsController;
|
||||
private final MainActivity act;
|
||||
|
||||
public MeetingImportController(MeetingsController meetingsController, MainActivity act) {
|
||||
this.meetingsController = meetingsController;
|
||||
this.act = act;
|
||||
}
|
||||
|
||||
public void handleSendText(Intent intent) {
|
||||
final String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
handleSendText(sharedText);
|
||||
}
|
||||
|
||||
public void handleSendText(String sharedText) {
|
||||
handleSendText(sharedText, false);
|
||||
}
|
||||
|
||||
public boolean handleSendText(String sharedText, boolean checkOnly) {
|
||||
String meetingID = "";
|
||||
String meetingPWD = "";
|
||||
String meetingName = "";
|
||||
|
||||
if (sharedText != null) {
|
||||
Matcher matcher = Pattern.compile("(?i)(?<=(Meeting-ID:)|(Meeting\\sID:)|(ID:)).+").matcher(sharedText);
|
||||
if (matcher.find()) {
|
||||
meetingID = Objects.requireNonNull(matcher.group(0)).trim();
|
||||
}
|
||||
matcher = Pattern.compile("(?i)(?<=(Kenncode:)|(Passwort:)|(Passcode:)|(Password:)).+").matcher(sharedText);
|
||||
if (matcher.find()) {
|
||||
meetingPWD = Objects.requireNonNull(matcher.group(0)).trim();
|
||||
}
|
||||
matcher = Pattern.compile("(?i)(?<=(Thema:)|(Meeting\\sName:)|(Topic:)).+").matcher(sharedText);
|
||||
if (matcher.find()) {
|
||||
meetingName = Objects.requireNonNull(matcher.group(0)).trim();
|
||||
}
|
||||
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(act);
|
||||
builder.setTitle(R.string.app_name);
|
||||
builder.setMessage(R.string.meetingAlreadyExists);
|
||||
|
||||
if (meetingID.equals("")) {
|
||||
builder.setMessage(R.string.importError);
|
||||
builder.setPositiveButton(R.string.ok, (dialog, which) -> dialog.dismiss());
|
||||
final AlertDialog alert = builder.create();
|
||||
if (!checkOnly) alert.show();
|
||||
return false;
|
||||
} else {
|
||||
|
||||
final Meeting mtgToImport = new Meeting(meetingName, meetingID, meetingPWD, "", "");
|
||||
|
||||
if (meetingsController.searchMeetingInList(meetingsController.getMeetings(), mtgToImport.getMeetingName()) == -1 && !checkOnly) {
|
||||
act.fillMeeting(mtgToImport);
|
||||
} else {
|
||||
builder.setPositiveButton(R.string.override, (dialog, which) -> {
|
||||
final int found = meetingsController.searchMeetingInList(meetingsController.getMeetings(), mtgToImport.getMeetingName());
|
||||
importMeeting(mtgToImport, false);
|
||||
act.fillMeeting(found);
|
||||
dialog.dismiss();
|
||||
});
|
||||
builder.setNegativeButton(R.string.rename, (dialog, which) -> {
|
||||
mtgToImport.setMeetingName("");
|
||||
act.fillMeeting(mtgToImport);
|
||||
dialog.dismiss();
|
||||
});
|
||||
final AlertDialog alert = builder.create();
|
||||
if (!checkOnly) alert.show();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void importMeeting(Meeting meeting, boolean updateAttendee) {
|
||||
final ArrayList<Meeting> listMeetings = meetingsController.getMeetings();
|
||||
final int foundCurr = meetingsController.searchMeetingInList(listMeetings, meeting.getMeetingName());
|
||||
|
||||
if (foundCurr == -1) {
|
||||
listMeetings.add(meeting);
|
||||
if (listMeetings.size() == 1) {
|
||||
meetingsController.setLastMeeting(0);
|
||||
} else {
|
||||
meetingsController.setLastMeeting(listMeetings.size() - 1);
|
||||
}
|
||||
|
||||
} else {
|
||||
final Meeting currMeeting = meetingsController.getMeetings().get(foundCurr);
|
||||
currMeeting.setMeetingID(meeting.getMeetingID());
|
||||
currMeeting.setMeetingPWD(meeting.getMeetingPWD());
|
||||
if (updateAttendee)
|
||||
currMeeting.updateAttendee(meeting.getAttendees().get(0).getName(), meeting.getAttendees().get(0).getNum());
|
||||
listMeetings.set(foundCurr, currMeeting);
|
||||
meetingsController.setLastMeeting(foundCurr);
|
||||
}
|
||||
meetingsController.saveMeetingList(listMeetings);
|
||||
act.fillDropdownMeetingName();
|
||||
act.fillDropdownAttendeeName(meetingsController.getLastMeeting());
|
||||
Toast.makeText(act, R.string.meetingSaved, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
public void importMeeting(Meeting meeting) {
|
||||
importMeeting(meeting, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package de.joel.zoomhelper;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Base64;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MeetingsController {
|
||||
private final Context context;
|
||||
private final Activity activity;
|
||||
|
||||
public MeetingsController(Context context) {
|
||||
this.context = context;
|
||||
this.activity = (Activity) context;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String meetingsToString(ArrayList<Meeting> listMeetings) {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
ObjectOutputStream objOutputStream = null;
|
||||
try {
|
||||
objOutputStream = new ObjectOutputStream(byteArrayOutputStream);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
for (Object obj : listMeetings) {
|
||||
|
||||
try {
|
||||
if (objOutputStream == null) throw new AssertionError();
|
||||
objOutputStream.writeObject(obj);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
objOutputStream.reset();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (objOutputStream == null) throw new AssertionError();
|
||||
objOutputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Base64.encodeToString(byteArrayOutputStream.toByteArray(), 0);
|
||||
}
|
||||
|
||||
|
||||
public ArrayList<Meeting> stringToMeetings(String string) {
|
||||
byte[] bytes = Base64.decode(string, 0);
|
||||
|
||||
ArrayList<Meeting> listMeetings = new ArrayList<>();
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
ObjectInputStream obj = null;
|
||||
try {
|
||||
obj = new ObjectInputStream(bis);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
while (bis.available() != -1) {
|
||||
//Read object from file
|
||||
if (obj == null) throw new AssertionError();
|
||||
Meeting meeting = (Meeting) obj.readObject();
|
||||
listMeetings.add(meeting);
|
||||
}
|
||||
} catch (EOFException ex) {
|
||||
//ex.printStackTrace();
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return listMeetings;
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
public void saveMeetingList(ArrayList<Meeting> meetingList) {
|
||||
SharedPreferences mPrefs = activity.getPreferences(Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor prefsEditor = mPrefs.edit();
|
||||
prefsEditor.putString("Meetings", meetingsToString(meetingList));
|
||||
prefsEditor.commit();
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
public void setLastMeeting(int id) {
|
||||
SharedPreferences mPrefs = activity.getPreferences(Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor prefsEditor = mPrefs.edit();
|
||||
prefsEditor.putInt("LastMeeting", id);
|
||||
prefsEditor.commit();
|
||||
}
|
||||
|
||||
public int getLastMeeting() {
|
||||
SharedPreferences mPrefs = activity.getPreferences(Context.MODE_PRIVATE);
|
||||
return mPrefs.getInt("LastMeeting", -1);
|
||||
}
|
||||
|
||||
|
||||
public ArrayList<Meeting> getMeetings() {
|
||||
SharedPreferences mPrefs = activity.getPreferences(Context.MODE_PRIVATE);
|
||||
String value = mPrefs.getString("Meetings", "");
|
||||
ArrayList<Meeting> meetingList;
|
||||
if (value.equals("")) {
|
||||
meetingList = new ArrayList<>();
|
||||
} else {
|
||||
meetingList = stringToMeetings(value);
|
||||
}
|
||||
return meetingList;
|
||||
}
|
||||
|
||||
//TODO: Nach Remove bleibt Meeting noch stehen
|
||||
public void removeMeeting(int meeting) {
|
||||
MainActivity act = (MainActivity)context;
|
||||
ArrayList<Meeting> meetings = getMeetings();
|
||||
if (meetings.size() > 0 && meeting != -1) {
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
|
||||
builder.setTitle(R.string.app_name);
|
||||
builder.setMessage(context.getString(R.string.suretoremove, meetings.get(meeting).getMeetingName()));
|
||||
|
||||
builder.setPositiveButton(R.string.yes, (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
meetings.remove(meeting);
|
||||
saveMeetingList(meetings);
|
||||
|
||||
if (meetings.size() > 1) {
|
||||
act.fillDropdownMeetingName();
|
||||
if (getLastMeeting() > meetings.size() - 1) {
|
||||
setLastMeeting(meetings.size() - 1);
|
||||
act.fillMeeting(getLastMeeting());
|
||||
}
|
||||
} else if (meetings.size() == 1) {
|
||||
setLastMeeting(0);
|
||||
act.fillDropdownMeetingName();
|
||||
act.fillMeeting(getLastMeeting());
|
||||
} else {
|
||||
setLastMeeting(-1);
|
||||
act.fillDropdownMeetingName();
|
||||
act.fillBlank();
|
||||
}
|
||||
});
|
||||
|
||||
builder.setNegativeButton(R.string.no, (dialog, which) -> dialog.dismiss());
|
||||
|
||||
AlertDialog alert = builder.create();
|
||||
alert.show();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public int searchMeetingInList(ArrayList<Meeting> meetings, String name) {
|
||||
int found = -1;
|
||||
for (int i = 0; i < meetings.size(); i++) {
|
||||
if (meetings.get(i).getMeetingName().equals(name)) {
|
||||
found = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package de.joel.zoomhelper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class ZoomLink {
|
||||
private final Meeting meeting;
|
||||
private final Context context;
|
||||
|
||||
public ZoomLink(Context context, Meeting meeting) {
|
||||
this.meeting = meeting;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private String buildZoomURL() {
|
||||
Uri.Builder builder = new Uri.Builder();
|
||||
builder.scheme("zoomus")
|
||||
.authority("zoom.us")
|
||||
.appendPath("join")
|
||||
.appendQueryParameter("confno", this.meeting.getMeetingID().replace(" ", ""))
|
||||
.appendQueryParameter("pwd", this.meeting.getMeetingPWD());
|
||||
|
||||
if (!Objects.equals(this.meeting.getAttendees().get(this.meeting.getLastAtt()).getNum(), "")) {
|
||||
builder.appendQueryParameter("uname", this.meeting.getAttendees().get(this.meeting.getLastAtt()).getName() + " (" + this.meeting.getAttendees().get(this.meeting.getLastAtt()).getNum() + ")");
|
||||
} else
|
||||
builder.appendQueryParameter("uname", this.meeting.getAttendees().get(this.meeting.getLastAtt()).getName());
|
||||
return builder.build().toString();
|
||||
}
|
||||
|
||||
public void launch() {
|
||||
String url = buildZoomURL();
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
if (intent.resolveActivity(context.getPackageManager()) != null) {
|
||||
context.startActivity(intent);
|
||||
}
|
||||
/*if (testing) {
|
||||
//For Debugging: Show URL in Alert
|
||||
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
|
||||
dialog.setMessage(url);
|
||||
dialog.setTitle("Zoom URL (for testing)");
|
||||
AlertDialog alertDialog = dialog.create();
|
||||
alertDialog.show();
|
||||
}*/
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,7 @@
|
||||
android:layout_marginTop="22dp"
|
||||
android:onClick="btnSave_onClick"
|
||||
android:text="@string/Save"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageTrash" />
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnJoin"
|
||||
|
||||
@@ -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>
|
||||
@@ -28,7 +28,7 @@
|
||||
<string name="alwaysAsk">Dialog immer anzeigen</string>
|
||||
<string name="wantToimportMeeting">Möchtest du das folgende Meeting importieren?</string>
|
||||
<string name="importError">"Dieser Text entspricht keinem für ZoomHelper bekannten Meetingeinladungs-Muster "</string>
|
||||
<string name="ok">ok</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="meetingAlreadyExists">Meeting unter diesem Namen existiert schon. Bitte umbenennen, ansonsten wird es beim Speichern überschrieben</string>
|
||||
<string name="override">überschreiben</string>
|
||||
<string name="rename">umbenennen</string>
|
||||
@@ -39,5 +39,11 @@
|
||||
<string name="cantCreateShortcut">Du musst das Meeting zunächst speichern, bevor du eine Verknüpfung dazu erstellen kannst</string>
|
||||
<string name="saveMeeting">Meeting speichern</string>
|
||||
<string name="cancel">abbrechen</string>
|
||||
<string name="meetingSaved">Meeting gespeichert</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>
|
||||
|
||||
</resources>
|
||||
Reference in New Issue
Block a user