diff --git a/.gitignore b/.gitignore index 40749a5..01256b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,10 @@ *.iml .gradle -/local.properties .DS_Store -/build -/app/release +**/build +**/release /captures .externalNativeBuild .cxx local.properties -/.idea/ +/.idea/ \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 7f52cb1..24d6805 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,7 +3,7 @@ apply from: "$rootProject.projectDir/shared-build.gradle" android { defaultConfig { - versionCode 35 + versionCode 45 wearAppUnbundled true } buildFeatures { @@ -12,23 +12,17 @@ android { } dependencies { - implementation 'androidx.appcompat:appcompat:1.4.2' - implementation 'com.google.android.material:material:1.6.1' + implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'com.google.android.material:material:1.7.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'androidx.navigation:navigation-fragment:2.4.2' - implementation 'androidx.navigation:navigation-ui:2.4.2' - implementation 'com.google.guava:guava:31.0.1-jre' + implementation 'androidx.navigation:navigation-fragment:2.5.3' + implementation 'androidx.navigation:navigation-ui:2.5.3' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' - def room_version = "2.4.2" + def room_version = "2.4.3" implementation "androidx.room:room-runtime:$room_version" annotationProcessor "androidx.room:room-compiler:$room_version" - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - implementation(name: 'android-ping', ext: 'aar') - - implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.3' + implementation project(path: ':shared-models') } \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 8a0dc3d..3e8f971 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,13 @@ # If you keep the line number information, uncomment this to # hide the original source file name. --renamesourcefileattribute SourceFile \ No newline at end of file +-renamesourcefileattribute SourceFile + +# Jackson +-keep @com.fasterxml.jackson.annotation.JsonIgnoreProperties class * { *; } +-keep class com.fasterxml.** { *; } +-keep class org.codehaus.** { *; } +-keepnames class com.fasterxml.jackson.** { *; } +-keepclassmembers public final enum com.fasterxml.jackson.annotation.JsonAutoDetect$Visibility { + public static final com.fasterxml.jackson.annotation.JsonAutoDetect$Visibility *; +} diff --git a/app/src/androidTest/java/de/florianisme/wakeonlan/ExampleInstrumentedTest.java b/app/src/androidTest/java/de/florianisme/wakeonlan/ExampleInstrumentedTest.java deleted file mode 100644 index c7944b6..0000000 --- a/app/src/androidTest/java/de/florianisme/wakeonlan/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package de.florianisme.wakeonlan; - -import android.content.Context; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - assertEquals("de.florianisme.wakeonlan", appContext.getPackageName()); - } -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a648d3b..64b7c8c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,15 +23,15 @@ diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png deleted file mode 100644 index 3689cef..0000000 Binary files a/app/src/main/ic_launcher-playstore.png and /dev/null differ diff --git a/app/src/main/java/de/florianisme/wakeonlan/persistence/AppDatabase.java b/app/src/main/java/de/florianisme/wakeonlan/persistence/AppDatabase.java index 2908518..6be59d9 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/persistence/AppDatabase.java +++ b/app/src/main/java/de/florianisme/wakeonlan/persistence/AppDatabase.java @@ -5,7 +5,7 @@ import de.florianisme.wakeonlan.persistence.entities.Device; -@Database(entities = {Device.class}, version = 2, exportSchema = false) +@Database(entities = {Device.class}, version = 3, exportSchema = false) public abstract class AppDatabase extends RoomDatabase { public abstract DeviceDao deviceDao(); } \ No newline at end of file diff --git a/app/src/main/java/de/florianisme/wakeonlan/persistence/DatabaseInstanceManager.java b/app/src/main/java/de/florianisme/wakeonlan/persistence/DatabaseInstanceManager.java index cfdd701..579a555 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/persistence/DatabaseInstanceManager.java +++ b/app/src/main/java/de/florianisme/wakeonlan/persistence/DatabaseInstanceManager.java @@ -5,6 +5,7 @@ import androidx.room.Room; import de.florianisme.wakeonlan.persistence.migrations.MigrationFrom1To2; +import de.florianisme.wakeonlan.persistence.migrations.MigrationFrom2To3; public class DatabaseInstanceManager { @@ -14,7 +15,7 @@ public static synchronized AppDatabase getInstance(Context context) { if (appDatabase == null) { appDatabase = Room.databaseBuilder(context, AppDatabase.class, "database-name") .allowMainThreadQueries() - .addMigrations(new MigrationFrom1To2()) + .addMigrations(new MigrationFrom1To2(), new MigrationFrom2To3()) .build(); } return appDatabase; diff --git a/app/src/main/java/de/florianisme/wakeonlan/persistence/entities/Device.java b/app/src/main/java/de/florianisme/wakeonlan/persistence/entities/Device.java index 5350516..2d046ff 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/persistence/entities/Device.java +++ b/app/src/main/java/de/florianisme/wakeonlan/persistence/entities/Device.java @@ -18,7 +18,7 @@ public class Device { public String macAddress; @ColumnInfo(name = "broadcast_address") - public String broadcast_address; + public String broadcastAddress; @ColumnInfo(name = "port") public int port; @@ -26,15 +26,20 @@ public class Device { @ColumnInfo(name = "status_ip") public String statusIp; + @ColumnInfo(name = "secure_on_password") + public String secureOnPassword; + @Ignore - public Device(String name, String macAddress, String broadcast_address, int port, String statusIp) { + public Device(String name, String macAddress, String broadcastAddress, int port, String statusIp, String secureOnPassword) { this.name = name; this.macAddress = macAddress; - this.broadcast_address = broadcast_address; + this.broadcastAddress = broadcastAddress; this.port = port; this.statusIp = statusIp; + this.secureOnPassword = secureOnPassword; } + public Device() { } } diff --git a/app/src/main/java/de/florianisme/wakeonlan/persistence/migrations/MigrationFrom2To3.java b/app/src/main/java/de/florianisme/wakeonlan/persistence/migrations/MigrationFrom2To3.java new file mode 100644 index 0000000..cd3b4ea --- /dev/null +++ b/app/src/main/java/de/florianisme/wakeonlan/persistence/migrations/MigrationFrom2To3.java @@ -0,0 +1,18 @@ +package de.florianisme.wakeonlan.persistence.migrations; + +import androidx.annotation.NonNull; +import androidx.room.migration.Migration; +import androidx.sqlite.db.SupportSQLiteDatabase; + +public class MigrationFrom2To3 extends Migration { + + public MigrationFrom2To3() { + super(2, 3); + } + + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + database.execSQL("ALTER TABLE 'Devices' ADD COLUMN 'secure_on_password' TEXT"); + } + +} diff --git a/app/src/main/java/de/florianisme/wakeonlan/status/DeviceStatusTester.java b/app/src/main/java/de/florianisme/wakeonlan/status/DeviceStatusTester.java deleted file mode 100644 index 959ef63..0000000 --- a/app/src/main/java/de/florianisme/wakeonlan/status/DeviceStatusTester.java +++ /dev/null @@ -1,10 +0,0 @@ -package de.florianisme.wakeonlan.status; - -import de.florianisme.wakeonlan.persistence.entities.Device; - -public interface DeviceStatusTester { - - void scheduleDeviceStatusPings(Device device, OnDeviceStatusAvailable onDeviceStatusAvailable); - - void stopDeviceStatusPings(); -} diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/MainActivity.java b/app/src/main/java/de/florianisme/wakeonlan/ui/MainActivity.java index e77b558..a33d950 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/MainActivity.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/MainActivity.java @@ -40,7 +40,7 @@ protected void onCreate(Bundle savedInstanceState) { } private void initializeWearClient() { - wearClient = new WearClient().init(this); + wearClient = new WearClient(this); DatabaseInstanceManager.getInstance(this).deviceDao() .getAllAsObservable() .observe(this, devices -> wearClient.onDeviceListUpdated(devices)); @@ -72,10 +72,4 @@ public void onBackPressed() { super.onBackPressed(); } } - - @Override - protected void onDestroy() { - super.onDestroy(); - wearClient.destroy(); - } } \ No newline at end of file diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/BackupFragment.java b/app/src/main/java/de/florianisme/wakeonlan/ui/backup/BackupFragment.java similarity index 96% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/BackupFragment.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/backup/BackupFragment.java index 7488f6f..4b06f7a 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/BackupFragment.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/backup/BackupFragment.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.backup; +package de.florianisme.wakeonlan.ui.backup; import android.os.Bundle; import android.view.LayoutInflater; diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/DataExporter.java b/app/src/main/java/de/florianisme/wakeonlan/ui/backup/DataExporter.java similarity index 89% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/DataExporter.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/backup/DataExporter.java index ac86a15..ffb409a 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/DataExporter.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/backup/DataExporter.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.backup; +package de.florianisme.wakeonlan.ui.backup; import android.content.Context; import android.net.Uri; @@ -10,6 +10,8 @@ import androidx.activity.result.ActivityResultLauncher; import androidx.fragment.app.Fragment; +import com.fasterxml.jackson.databind.ObjectMapper; + import java.io.FileOutputStream; import java.lang.ref.WeakReference; import java.util.List; @@ -17,7 +19,7 @@ import de.florianisme.wakeonlan.R; import de.florianisme.wakeonlan.persistence.DatabaseInstanceManager; import de.florianisme.wakeonlan.persistence.entities.Device; -import de.florianisme.wakeonlan.ui.home.backup.contracts.ChooseSaveFileDestinationContract; +import de.florianisme.wakeonlan.ui.backup.contracts.ChooseSaveFileDestinationContract; public class DataExporter implements ActivityResultCallback { @@ -44,7 +46,7 @@ public void onActivityResult(Uri uri) { Context context = contextWeakReference.get(); try { List devices = DatabaseInstanceManager.getInstance(context).deviceDao().getAll(); - byte[] content = JsonConverter.toJson(devices); + byte[] content = new ObjectMapper().writeValueAsBytes(devices); writeDevicesToFile(uri, content, context); Toast.makeText(context, context.getString(R.string.backup_message_export_success, devices.size()), Toast.LENGTH_SHORT).show(); diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/DataImporter.java b/app/src/main/java/de/florianisme/wakeonlan/ui/backup/DataImporter.java similarity index 89% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/DataImporter.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/backup/DataImporter.java index 1b6dc9d..cc1344e 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/DataImporter.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/backup/DataImporter.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.backup; +package de.florianisme.wakeonlan.ui.backup; import android.content.Context; import android.net.Uri; @@ -9,6 +9,7 @@ import androidx.activity.result.ActivityResultLauncher; import androidx.fragment.app.Fragment; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.io.ByteStreams; import java.io.IOException; @@ -19,7 +20,7 @@ import de.florianisme.wakeonlan.persistence.DatabaseInstanceManager; import de.florianisme.wakeonlan.persistence.DeviceDao; import de.florianisme.wakeonlan.persistence.entities.Device; -import de.florianisme.wakeonlan.ui.home.backup.contracts.ChooseImportFileDestinationContract; +import de.florianisme.wakeonlan.ui.backup.contracts.ChooseImportFileDestinationContract; public class DataImporter implements ActivityResultCallback { @@ -44,7 +45,7 @@ public void onActivityResult(Uri uri) { try { byte[] bytes = readContentFromFile(uri, context); - Device[] devices = JsonConverter.toModel(bytes); + Device[] devices = new ObjectMapper().readValue(bytes, Device[].class); replaceDevicesInDatabase(devices, context); Toast.makeText(context, context.getString(R.string.backup_message_import_success, devices.length), Toast.LENGTH_LONG).show(); diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/contracts/ChooseImportFileDestinationContract.java b/app/src/main/java/de/florianisme/wakeonlan/ui/backup/contracts/ChooseImportFileDestinationContract.java similarity index 93% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/contracts/ChooseImportFileDestinationContract.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/backup/contracts/ChooseImportFileDestinationContract.java index 5fbb21d..d9b4f2d 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/contracts/ChooseImportFileDestinationContract.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/backup/contracts/ChooseImportFileDestinationContract.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.backup.contracts; +package de.florianisme.wakeonlan.ui.backup.contracts; import android.app.Activity; import android.content.Context; diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/contracts/ChooseSaveFileDestinationContract.java b/app/src/main/java/de/florianisme/wakeonlan/ui/backup/contracts/ChooseSaveFileDestinationContract.java similarity index 94% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/contracts/ChooseSaveFileDestinationContract.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/backup/contracts/ChooseSaveFileDestinationContract.java index ca346e2..75e15e4 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/contracts/ChooseSaveFileDestinationContract.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/backup/contracts/ChooseSaveFileDestinationContract.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.backup.contracts; +package de.florianisme.wakeonlan.ui.backup.contracts; import android.app.Activity; import android.content.Context; diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/JsonConverter.java b/app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/JsonConverter.java deleted file mode 100644 index 0ede440..0000000 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/backup/JsonConverter.java +++ /dev/null @@ -1,20 +0,0 @@ -package de.florianisme.wakeonlan.ui.home.backup; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import java.io.IOException; -import java.util.List; - -import de.florianisme.wakeonlan.persistence.entities.Device; - -public class JsonConverter { - - public static byte[] toJson(List devices) throws JsonProcessingException { - return new ObjectMapper().writeValueAsBytes(devices); - } - - public static Device[] toModel(byte[] content) throws IOException { - return new ObjectMapper().readValue(content, Device[].class); - } -} diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/validator/ValidationResult.java b/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/validator/ValidationResult.java deleted file mode 100644 index efed941..0000000 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/validator/ValidationResult.java +++ /dev/null @@ -1,5 +0,0 @@ -package de.florianisme.wakeonlan.ui.home.details.watcher.validator; - -public enum ValidationResult { - VALID, INVALID -} diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/DeviceDiffCallback.java b/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/DeviceDiffCallback.java deleted file mode 100644 index bbe7b3a..0000000 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/DeviceDiffCallback.java +++ /dev/null @@ -1,24 +0,0 @@ -package de.florianisme.wakeonlan.ui.home.list; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.DiffUtil; - -import com.google.common.base.Strings; - -import de.florianisme.wakeonlan.persistence.entities.Device; - -public class DeviceDiffCallback extends DiffUtil.ItemCallback { - - @Override - public boolean areItemsTheSame(@NonNull Device oldDevice, @NonNull Device newDevice) { - return oldDevice.id == newDevice.id; - } - - @Override - public boolean areContentsTheSame(@NonNull Device oldDevice, @NonNull Device newDevice) { - return Strings.nullToEmpty(oldDevice.name).equals(newDevice.name) && - Strings.nullToEmpty(oldDevice.broadcast_address).equals(newDevice.broadcast_address) && - Strings.nullToEmpty(oldDevice.statusIp).equals(newDevice.statusIp) && - oldDevice.port == newDevice.port; - } -} diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/viewholder/ListViewType.java b/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/viewholder/ListViewType.java deleted file mode 100644 index 2b0af1a..0000000 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/viewholder/ListViewType.java +++ /dev/null @@ -1,5 +0,0 @@ -package de.florianisme.wakeonlan.ui.home.list.viewholder; - -public enum ListViewType { - EMPTY, DEVICE -} diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/viewholder/ListViewType.java b/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/viewholder/ListViewType.java deleted file mode 100644 index acb2798..0000000 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/viewholder/ListViewType.java +++ /dev/null @@ -1,5 +0,0 @@ -package de.florianisme.wakeonlan.ui.home.scan.viewholder; - -public enum ListViewType { - EMPTY, SCAN_DEVICE -} diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/DeviceClickedCallback.java b/app/src/main/java/de/florianisme/wakeonlan/ui/list/DeviceClickedCallback.java similarity index 65% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/list/DeviceClickedCallback.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/list/DeviceClickedCallback.java index 506d742..0fa30a1 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/DeviceClickedCallback.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/list/DeviceClickedCallback.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.list; +package de.florianisme.wakeonlan.ui.list; public interface DeviceClickedCallback { void onDeviceClicked(String deviceName); diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/list/DeviceDiffCallback.java b/app/src/main/java/de/florianisme/wakeonlan/ui/list/DeviceDiffCallback.java new file mode 100644 index 0000000..15594c4 --- /dev/null +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/list/DeviceDiffCallback.java @@ -0,0 +1,31 @@ +package de.florianisme.wakeonlan.ui.list; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.DiffUtil; + +import com.google.common.base.Strings; + +import de.florianisme.wakeonlan.persistence.entities.Device; + +public class DeviceDiffCallback extends DiffUtil.ItemCallback { + + @Override + public boolean areItemsTheSame(@NonNull Device oldDevice, @NonNull Device newDevice) { + return oldDevice.id == newDevice.id; + } + + @Override + public boolean areContentsTheSame(@NonNull Device oldDevice, @NonNull Device newDevice) { + return stringMatches(oldDevice.name, newDevice.name) && + stringMatches(oldDevice.broadcastAddress, newDevice.broadcastAddress) && + stringMatches(oldDevice.statusIp, newDevice.statusIp) && + stringMatches(oldDevice.macAddress, newDevice.macAddress) && + stringMatches(oldDevice.secureOnPassword, newDevice.secureOnPassword) && + oldDevice.port == newDevice.port; + } + + private boolean stringMatches(@Nullable String oldString, @Nullable String newString) { + return Strings.nullToEmpty(oldString).equals(newString); + } +} diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/DeviceListAdapter.java b/app/src/main/java/de/florianisme/wakeonlan/ui/list/DeviceListAdapter.java similarity index 92% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/list/DeviceListAdapter.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/list/DeviceListAdapter.java index 768c06d..25c6ece 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/DeviceListAdapter.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/list/DeviceListAdapter.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.list; +package de.florianisme.wakeonlan.ui.list; import android.view.LayoutInflater; import android.view.View; @@ -12,9 +12,9 @@ import de.florianisme.wakeonlan.R; import de.florianisme.wakeonlan.persistence.entities.Device; -import de.florianisme.wakeonlan.ui.home.list.viewholder.DeviceItemViewHolder; -import de.florianisme.wakeonlan.ui.home.list.viewholder.EmptyViewHolder; -import de.florianisme.wakeonlan.ui.home.list.viewholder.ListViewType; +import de.florianisme.wakeonlan.ui.list.viewholder.DeviceItemViewHolder; +import de.florianisme.wakeonlan.ui.list.viewholder.EmptyViewHolder; +import de.florianisme.wakeonlan.ui.list.viewholder.ListViewType; public class DeviceListAdapter extends RecyclerView.Adapter { diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/DeviceListFragment.java b/app/src/main/java/de/florianisme/wakeonlan/ui/list/DeviceListFragment.java similarity index 82% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/list/DeviceListFragment.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/list/DeviceListFragment.java index df74993..48312b1 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/DeviceListFragment.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/list/DeviceListFragment.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.list; +package de.florianisme.wakeonlan.ui.list; import android.os.Bundle; import android.view.LayoutInflater; @@ -18,6 +18,8 @@ import de.florianisme.wakeonlan.databinding.FragmentListDevicesBinding; import de.florianisme.wakeonlan.persistence.DatabaseInstanceManager; import de.florianisme.wakeonlan.persistence.entities.Device; +import de.florianisme.wakeonlan.ui.list.layoutmanager.GridLayoutManagerWrapper; +import de.florianisme.wakeonlan.ui.list.layoutmanager.LinearLayoutManagerWrapper; public class DeviceListFragment extends Fragment { @@ -55,7 +57,16 @@ private void instantiateRecyclerView() { RecyclerView devicesRecyclerView = binding.machineList; devicesRecyclerView.setAdapter(deviceListAdapter); - devicesRecyclerView.setLayoutManager(new LinearLayoutManagerWrapper(getContext())); + devicesRecyclerView.setLayoutManager(getLayoutManager()); + } + + @NonNull + private RecyclerView.LayoutManager getLayoutManager() { + if (getResources().getBoolean(R.bool.isTablet)) { + return new GridLayoutManagerWrapper(getContext(), 2); + } else { + return new LinearLayoutManagerWrapper(getContext()); + } } @NonNull diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/list/layoutmanager/GridLayoutManagerWrapper.java b/app/src/main/java/de/florianisme/wakeonlan/ui/list/layoutmanager/GridLayoutManagerWrapper.java new file mode 100644 index 0000000..a16fcdf --- /dev/null +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/list/layoutmanager/GridLayoutManagerWrapper.java @@ -0,0 +1,17 @@ +package de.florianisme.wakeonlan.ui.list.layoutmanager; + +import android.content.Context; + +import androidx.recyclerview.widget.GridLayoutManager; + +public class GridLayoutManagerWrapper extends GridLayoutManager { + + public GridLayoutManagerWrapper(Context context, int spanCount) { + super(context, spanCount); + } + + @Override + public boolean supportsPredictiveItemAnimations() { + return false; + } +} \ No newline at end of file diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/LinearLayoutManagerWrapper.java b/app/src/main/java/de/florianisme/wakeonlan/ui/list/layoutmanager/LinearLayoutManagerWrapper.java similarity index 86% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/list/LinearLayoutManagerWrapper.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/list/layoutmanager/LinearLayoutManagerWrapper.java index d2bfd1a..874907c 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/LinearLayoutManagerWrapper.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/list/layoutmanager/LinearLayoutManagerWrapper.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.list; +package de.florianisme.wakeonlan.ui.list.layoutmanager; import android.content.Context; diff --git a/app/src/main/java/de/florianisme/wakeonlan/status/OnDeviceStatusAvailable.java b/app/src/main/java/de/florianisme/wakeonlan/ui/list/status/DeviceStatusListener.java similarity index 58% rename from app/src/main/java/de/florianisme/wakeonlan/status/OnDeviceStatusAvailable.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/list/status/DeviceStatusListener.java index 9155f09..a8f5107 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/status/OnDeviceStatusAvailable.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/list/status/DeviceStatusListener.java @@ -1,8 +1,8 @@ -package de.florianisme.wakeonlan.status; +package de.florianisme.wakeonlan.ui.list.status; import de.florianisme.wakeonlan.persistence.models.DeviceStatus; -public interface OnDeviceStatusAvailable { +public interface DeviceStatusListener { void onStatusAvailable(DeviceStatus deviceStatus); diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/list/status/DeviceStatusTester.java b/app/src/main/java/de/florianisme/wakeonlan/ui/list/status/DeviceStatusTester.java new file mode 100644 index 0000000..8a540f9 --- /dev/null +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/list/status/DeviceStatusTester.java @@ -0,0 +1,10 @@ +package de.florianisme.wakeonlan.ui.list.status; + +import de.florianisme.wakeonlan.persistence.entities.Device; + +public interface DeviceStatusTester { + + void scheduleDeviceStatusPings(Device device, DeviceStatusListener deviceStatusListener); + + void stopDeviceStatusPings(); +} diff --git a/app/src/main/java/de/florianisme/wakeonlan/status/PingDeviceStatusTester.java b/app/src/main/java/de/florianisme/wakeonlan/ui/list/status/PingDeviceStatusTester.java similarity index 77% rename from app/src/main/java/de/florianisme/wakeonlan/status/PingDeviceStatusTester.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/list/status/PingDeviceStatusTester.java index 0fbeeae..3cb9b26 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/status/PingDeviceStatusTester.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/list/status/PingDeviceStatusTester.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.status; +package de.florianisme.wakeonlan.ui.list.status; import android.util.Log; @@ -17,11 +17,11 @@ public class PingDeviceStatusTester implements DeviceStatusTester { private ScheduledExecutorService pingExecutor; @Override - public void scheduleDeviceStatusPings(Device device, OnDeviceStatusAvailable onDeviceStatusAvailable) { + public void scheduleDeviceStatusPings(Device device, DeviceStatusListener deviceStatusListener) { pingExecutor = Executors.newSingleThreadScheduledExecutor(); pingExecutor.scheduleWithFixedDelay(() -> { if (device.statusIp == null || device.statusIp.isEmpty()) { - onDeviceStatusAvailable.onStatusAvailable(DeviceStatus.UNKNOWN); + deviceStatusListener.onStatusAvailable(DeviceStatus.UNKNOWN); return; } @@ -32,16 +32,16 @@ public void scheduleDeviceStatusPings(Device device, OnDeviceStatusAvailable onD public void onPing(final long timeMs, final int count) { if (timeMs == -1L) { Log.w(getClass().getSimpleName(), String.format("Ping timed out for IP %s", device.statusIp)); - onDeviceStatusAvailable.onStatusAvailable(DeviceStatus.OFFLINE); + deviceStatusListener.onStatusAvailable(DeviceStatus.OFFLINE); return; } - onDeviceStatusAvailable.onStatusAvailable(DeviceStatus.ONLINE); + deviceStatusListener.onStatusAvailable(DeviceStatus.ONLINE); } @Override public void onPingException(final Exception e, final int count) { Log.w(getClass().getSimpleName(), String.format("Error while pinging device with IP %s", device.statusIp), e); - onDeviceStatusAvailable.onStatusAvailable(DeviceStatus.OFFLINE); + deviceStatusListener.onStatusAvailable(DeviceStatus.OFFLINE); } }); ping.setCount(1); @@ -49,7 +49,7 @@ public void onPingException(final Exception e, final int count) { ping.run(); } catch (Exception e) { Log.w(getClass().getSimpleName(), String.format("Error while pinging device with IP %s", device.statusIp), e); - onDeviceStatusAvailable.onStatusAvailable(DeviceStatus.UNKNOWN); + deviceStatusListener.onStatusAvailable(DeviceStatus.UNKNOWN); } }, 0, 4000, TimeUnit.MILLISECONDS); } diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/viewholder/DeviceItemViewHolder.java b/app/src/main/java/de/florianisme/wakeonlan/ui/list/viewholder/DeviceItemViewHolder.java similarity index 79% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/list/viewholder/DeviceItemViewHolder.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/list/viewholder/DeviceItemViewHolder.java index f1e4210..153787f 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/viewholder/DeviceItemViewHolder.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/list/viewholder/DeviceItemViewHolder.java @@ -1,7 +1,9 @@ -package de.florianisme.wakeonlan.ui.home.list.viewholder; +package de.florianisme.wakeonlan.ui.list.viewholder; import android.content.Context; import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; import android.os.Bundle; import android.view.View; import android.view.animation.AccelerateInterpolator; @@ -16,10 +18,10 @@ import de.florianisme.wakeonlan.R; import de.florianisme.wakeonlan.persistence.entities.Device; import de.florianisme.wakeonlan.persistence.models.DeviceStatus; -import de.florianisme.wakeonlan.status.DeviceStatusTester; -import de.florianisme.wakeonlan.status.PingDeviceStatusTester; -import de.florianisme.wakeonlan.ui.home.details.EditDeviceActivity; -import de.florianisme.wakeonlan.ui.home.list.DeviceClickedCallback; +import de.florianisme.wakeonlan.ui.list.DeviceClickedCallback; +import de.florianisme.wakeonlan.ui.list.status.DeviceStatusTester; +import de.florianisme.wakeonlan.ui.list.status.PingDeviceStatusTester; +import de.florianisme.wakeonlan.ui.modify.EditDeviceActivity; import de.florianisme.wakeonlan.wol.WolSender; public class DeviceItemViewHolder extends RecyclerView.ViewHolder { @@ -79,16 +81,26 @@ public void startDeviceStatusQuery(Device device) { deviceStatusTester.scheduleDeviceStatusPings(device, status -> { if (status == DeviceStatus.ONLINE) { setAlphaAnimationIfNotSet(); - deviceStatus.setBackground(AppCompatResources.getDrawable(itemView.getContext(), R.drawable.device_status_online)); + setStatusDrawable(R.drawable.device_status_online); } else if (status == DeviceStatus.OFFLINE) { setAlphaAnimationIfNotSet(); - deviceStatus.setBackground(AppCompatResources.getDrawable(itemView.getContext(), R.drawable.device_status_offline)); + setStatusDrawable(R.drawable.device_status_offline); } else { deviceStatus.clearAnimation(); deviceStatus.setBackground(AppCompatResources.getDrawable(itemView.getContext(), R.drawable.device_status_unknown)); } }); + } + + private void setStatusDrawable(int statusDrawable) { + Drawable[] drawables = { + deviceStatus.getBackground(), + AppCompatResources.getDrawable(itemView.getContext(), statusDrawable) + }; + TransitionDrawable transitionDrawable = new TransitionDrawable(drawables); + deviceStatus.setBackground(transitionDrawable); + transitionDrawable.startTransition(600); } private void setAlphaAnimationIfNotSet() { diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/viewholder/EmptyViewHolder.java b/app/src/main/java/de/florianisme/wakeonlan/ui/list/viewholder/EmptyViewHolder.java similarity index 78% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/list/viewholder/EmptyViewHolder.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/list/viewholder/EmptyViewHolder.java index c1901bc..b278993 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/list/viewholder/EmptyViewHolder.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/list/viewholder/EmptyViewHolder.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.list.viewholder; +package de.florianisme.wakeonlan.ui.list.viewholder; import android.view.View; diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/list/viewholder/ListViewType.java b/app/src/main/java/de/florianisme/wakeonlan/ui/list/viewholder/ListViewType.java new file mode 100644 index 0000000..e83a3ee --- /dev/null +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/list/viewholder/ListViewType.java @@ -0,0 +1,5 @@ +package de.florianisme.wakeonlan.ui.list.viewholder; + +public enum ListViewType { + EMPTY, DEVICE +} diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/AddDeviceActivity.java b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/AddDeviceActivity.java similarity index 83% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/details/AddDeviceActivity.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/modify/AddDeviceActivity.java index 6243362..706946c 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/AddDeviceActivity.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/AddDeviceActivity.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.details; +package de.florianisme.wakeonlan.ui.modify; import android.view.Menu; import android.view.MenuItem; @@ -33,8 +33,9 @@ protected void persistDevice() { device.name = getDeviceNameInputText(); device.statusIp = getDeviceStatusIpText(); device.macAddress = getDeviceMacInputText(); - device.broadcast_address = getDeviceBroadcastAddressText(); + device.broadcastAddress = getDeviceBroadcastAddressText(); device.port = getPort(); + device.secureOnPassword = getDeviceSecureOnPassword(); databaseInstance.deviceDao().insertAll(device); } @@ -43,6 +44,7 @@ protected void persistDevice() { protected boolean inputsHaveNotChanged() { // There is no persisted device yet, so we check if any of our inputs are edited return getDeviceNameInputText().isEmpty() && getDeviceMacInputText().isEmpty() - && getDeviceBroadcastAddressText().isEmpty() && getDeviceStatusIpText().isEmpty(); + && getDeviceBroadcastAddressText().isEmpty() && getDeviceStatusIpText().isEmpty() + && getDeviceSecureOnPassword().isEmpty(); } } diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/AddNetworkScanDeviceActivity.java b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/AddNetworkScanDeviceActivity.java similarity index 94% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/details/AddNetworkScanDeviceActivity.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/modify/AddNetworkScanDeviceActivity.java index 7742736..3cf28b0 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/AddNetworkScanDeviceActivity.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/AddNetworkScanDeviceActivity.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.details; +package de.florianisme.wakeonlan.ui.modify; import android.os.Bundle; diff --git a/app/src/main/java/de/florianisme/wakeonlan/util/BroadcastHelper.java b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/BroadcastHelper.java similarity index 96% rename from app/src/main/java/de/florianisme/wakeonlan/util/BroadcastHelper.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/modify/BroadcastHelper.java index c4e7b52..2f01619 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/util/BroadcastHelper.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/BroadcastHelper.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.util; +package de.florianisme.wakeonlan.ui.modify; import java.io.IOException; import java.net.InetAddress; diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/EditDeviceActivity.java b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/EditDeviceActivity.java similarity index 83% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/details/EditDeviceActivity.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/modify/EditDeviceActivity.java index d971d7e..0b25cff 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/EditDeviceActivity.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/EditDeviceActivity.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.details; +package de.florianisme.wakeonlan.ui.modify; import android.os.Bundle; import android.view.Menu; @@ -8,6 +8,8 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import com.google.common.base.Strings; + import de.florianisme.wakeonlan.R; import de.florianisme.wakeonlan.persistence.entities.Device; @@ -37,7 +39,8 @@ private void populateInputs() { deviceNameInput.setText(device.name); deviceStatusIpInput.setText(device.statusIp); deviceMacInput.setText(device.macAddress); - deviceBroadcastInput.setText(device.broadcast_address); + deviceBroadcastInput.setText(device.broadcastAddress); + deviceSecureOnPassword.setText(device.secureOnPassword); if (device.port == 9) { devicePorts.setText("9", false); } else { @@ -77,10 +80,11 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { @Override protected boolean inputsHaveNotChanged() { return device.name.equals(getDeviceNameInputText()) && - device.broadcast_address.equals(getDeviceBroadcastAddressText()) && + device.broadcastAddress.equals(getDeviceBroadcastAddressText()) && device.macAddress.equals(getDeviceMacInputText()) && device.statusIp.equals(getDeviceStatusIpText()) && - device.port == getPort(); + device.port == getPort() && + Strings.nullToEmpty(device.secureOnPassword).equals(getDeviceSecureOnPassword()); } @Override @@ -88,8 +92,9 @@ protected void persistDevice() { device.name = getDeviceNameInputText(); device.statusIp = getDeviceStatusIpText(); device.macAddress = getDeviceMacInputText(); - device.broadcast_address = getDeviceBroadcastAddressText(); + device.broadcastAddress = getDeviceBroadcastAddressText(); device.port = getPort(); + device.secureOnPassword = getDeviceSecureOnPassword(); databaseInstance.deviceDao().update(device); } diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/ModifyDeviceActivity.java b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/ModifyDeviceActivity.java similarity index 85% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/details/ModifyDeviceActivity.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/modify/ModifyDeviceActivity.java index 4135e64..05b5b09 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/ModifyDeviceActivity.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/ModifyDeviceActivity.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.details; +package de.florianisme.wakeonlan.ui.modify; import android.os.Bundle; import android.util.Log; @@ -22,11 +22,11 @@ import de.florianisme.wakeonlan.databinding.ActivityModifyDeviceBinding; import de.florianisme.wakeonlan.persistence.AppDatabase; import de.florianisme.wakeonlan.persistence.DatabaseInstanceManager; -import de.florianisme.wakeonlan.ui.home.details.watcher.autocomplete.MacAddressAutocomplete; -import de.florianisme.wakeonlan.ui.home.details.watcher.validator.InputNotEmptyValidator; -import de.florianisme.wakeonlan.ui.home.details.watcher.validator.IpAddressValidator; -import de.florianisme.wakeonlan.ui.home.details.watcher.validator.MacValidator; -import de.florianisme.wakeonlan.util.BroadcastHelper; +import de.florianisme.wakeonlan.ui.modify.watcher.autocomplete.MacAddressAutocomplete; +import de.florianisme.wakeonlan.ui.modify.watcher.validator.InputNotEmptyValidator; +import de.florianisme.wakeonlan.ui.modify.watcher.validator.IpAddressValidator; +import de.florianisme.wakeonlan.ui.modify.watcher.validator.MacValidator; +import de.florianisme.wakeonlan.ui.modify.watcher.validator.SecureOnPasswordValidator; public abstract class ModifyDeviceActivity extends AppCompatActivity { @@ -37,6 +37,7 @@ public abstract class ModifyDeviceActivity extends AppCompatActivity { protected TextInputEditText deviceNameInput; protected TextInputEditText deviceStatusIpInput; protected TextInputEditText deviceBroadcastInput; + protected TextInputEditText deviceSecureOnPassword; protected ImageButton broadcastAutofill; protected MaterialAutoCompleteTextView devicePorts; @@ -52,6 +53,7 @@ protected void onCreate(Bundle savedInstanceState) { deviceNameInput = binding.device.deviceName; deviceStatusIpInput = binding.device.deviceStatusIp; deviceBroadcastInput = binding.device.deviceBroadcast; + deviceSecureOnPassword = binding.device.deviceSecureOnPassword; broadcastAutofill = binding.device.broadcastAutofill; setSupportActionBar(binding.toolbar); @@ -84,13 +86,14 @@ private void addValidators() { deviceNameInput.addTextChangedListener(new InputNotEmptyValidator(deviceNameInput)); deviceBroadcastInput.addTextChangedListener(new IpAddressValidator(deviceBroadcastInput)); + deviceSecureOnPassword.addTextChangedListener(new SecureOnPasswordValidator(deviceSecureOnPassword)); } protected boolean assertInputsNotEmptyAndValid() { return deviceMacInput.getError() == null && isNotEmpty(deviceMacInput) && deviceNameInput.getError() == null && isNotEmpty(deviceNameInput) && deviceBroadcastInput.getError() == null && isNotEmpty(deviceBroadcastInput) && - deviceStatusIpInput.getError() == null; + deviceStatusIpInput.getError() == null && deviceSecureOnPassword.getError() == null; } private boolean isNotEmpty(TextInputEditText inputEditText) { @@ -129,8 +132,8 @@ protected int getPort() { } @NonNull - private String getInputText(TextInputEditText deviceBroadcastInput) { - return deviceBroadcastInput.getText().toString().trim(); + private String getInputText(TextInputEditText testInput) { + return testInput.getText().toString().trim(); } @NonNull @@ -153,6 +156,11 @@ protected String getDeviceStatusIpText() { return getInputText(deviceStatusIpInput); } + @NonNull + protected String getDeviceSecureOnPassword() { + return getInputText(deviceSecureOnPassword); + } + @Override public boolean onSupportNavigateUp() { if (inputsHaveNotChanged()) { diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/autocomplete/MacAddressAutocomplete.java b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/autocomplete/MacAddressAutocomplete.java similarity index 96% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/autocomplete/MacAddressAutocomplete.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/autocomplete/MacAddressAutocomplete.java index d8cd506..7b50492 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/autocomplete/MacAddressAutocomplete.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/autocomplete/MacAddressAutocomplete.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.details.watcher.autocomplete; +package de.florianisme.wakeonlan.ui.modify.watcher.autocomplete; import android.text.Editable; import android.text.TextWatcher; diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/validator/InputNotEmptyValidator.java b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/InputNotEmptyValidator.java similarity index 88% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/validator/InputNotEmptyValidator.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/InputNotEmptyValidator.java index a8f2053..c727c35 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/validator/InputNotEmptyValidator.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/InputNotEmptyValidator.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.details.watcher.validator; +package de.florianisme.wakeonlan.ui.modify.watcher.validator; import android.widget.EditText; diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/validator/IpAddressValidator.java b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/IpAddressValidator.java similarity index 94% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/validator/IpAddressValidator.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/IpAddressValidator.java index e7e3448..e6f67ee 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/validator/IpAddressValidator.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/IpAddressValidator.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.details.watcher.validator; +package de.florianisme.wakeonlan.ui.modify.watcher.validator; import android.widget.EditText; diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/validator/MacValidator.java b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/MacValidator.java similarity index 91% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/validator/MacValidator.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/MacValidator.java index 4a2a67a..2654a8d 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/validator/MacValidator.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/MacValidator.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.details.watcher.validator; +package de.florianisme.wakeonlan.ui.modify.watcher.validator; import android.widget.EditText; diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/SecureOnPasswordValidator.java b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/SecureOnPasswordValidator.java new file mode 100644 index 0000000..951a6c3 --- /dev/null +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/SecureOnPasswordValidator.java @@ -0,0 +1,40 @@ +package de.florianisme.wakeonlan.ui.modify.watcher.validator; + +import android.widget.EditText; + +import com.google.common.base.Strings; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import de.florianisme.wakeonlan.R; + +public class SecureOnPasswordValidator extends Validator { + + private static final Charset CHARSET = StandardCharsets.US_ASCII; + + private final IpAddressValidator ipAddressValidator; + private final MacValidator macValidator; + + public SecureOnPasswordValidator(EditText editTextView) { + super(editTextView); + ipAddressValidator = new IpAddressValidator(editTextView); + macValidator = new MacValidator(editTextView); + } + + @Override + ValidationResult validate(String text) { + int passwordBytesLength = Strings.nullToEmpty(text).getBytes(CHARSET).length; + + if (passwordBytesLength == 0 || ipAddressValidator.validate(text) == ValidationResult.VALID || macValidator.validate(text) == ValidationResult.VALID) { + return ValidationResult.VALID; + } + + return ValidationResult.INVALID; + } + + @Override + int getErrorMessageStringId() { + return R.string.add_device_error_secure_on_password_invalid; + } +} diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/ValidationResult.java b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/ValidationResult.java new file mode 100644 index 0000000..0e08c6f --- /dev/null +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/ValidationResult.java @@ -0,0 +1,5 @@ +package de.florianisme.wakeonlan.ui.modify.watcher.validator; + +public enum ValidationResult { + VALID, INVALID +} diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/validator/Validator.java b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/Validator.java similarity index 94% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/validator/Validator.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/Validator.java index 13e88a9..2d62c7d 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/details/watcher/validator/Validator.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/modify/watcher/validator/Validator.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.details.watcher.validator; +package de.florianisme.wakeonlan.ui.modify.watcher.validator; import android.text.Editable; import android.text.TextWatcher; diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/NetworkScanAdapter.java b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/NetworkScanAdapter.java similarity index 91% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/NetworkScanAdapter.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/scan/NetworkScanAdapter.java index b639414..605d938 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/NetworkScanAdapter.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/NetworkScanAdapter.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.scan; +package de.florianisme.wakeonlan.ui.scan; import android.view.LayoutInflater; import android.view.View; @@ -16,10 +16,10 @@ import java.util.stream.Collectors; import de.florianisme.wakeonlan.R; -import de.florianisme.wakeonlan.ui.home.scan.model.NetworkScanDevice; -import de.florianisme.wakeonlan.ui.home.scan.viewholder.EmptyViewHolder; -import de.florianisme.wakeonlan.ui.home.scan.viewholder.ListViewType; -import de.florianisme.wakeonlan.ui.home.scan.viewholder.ScanResultViewHolder; +import de.florianisme.wakeonlan.ui.scan.model.NetworkScanDevice; +import de.florianisme.wakeonlan.ui.scan.viewholder.EmptyViewHolder; +import de.florianisme.wakeonlan.ui.scan.viewholder.ListViewType; +import de.florianisme.wakeonlan.ui.scan.viewholder.ScanResultViewHolder; public class NetworkScanAdapter extends RecyclerView.Adapter { diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/NetworkScanDiffCallback.java b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/NetworkScanDiffCallback.java similarity index 86% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/NetworkScanDiffCallback.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/scan/NetworkScanDiffCallback.java index d27a866..d04cb16 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/NetworkScanDiffCallback.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/NetworkScanDiffCallback.java @@ -1,11 +1,11 @@ -package de.florianisme.wakeonlan.ui.home.scan; +package de.florianisme.wakeonlan.ui.scan; import androidx.annotation.NonNull; import androidx.recyclerview.widget.DiffUtil; import com.google.common.base.Strings; -import de.florianisme.wakeonlan.ui.home.scan.model.NetworkScanDevice; +import de.florianisme.wakeonlan.ui.scan.model.NetworkScanDevice; public class NetworkScanDiffCallback extends DiffUtil.ItemCallback { diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/NetworkScanFragment.java b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/NetworkScanFragment.java similarity index 92% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/NetworkScanFragment.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/scan/NetworkScanFragment.java index 6e3aba1..fd58362 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/NetworkScanFragment.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/NetworkScanFragment.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.scan; +package de.florianisme.wakeonlan.ui.scan; import android.os.Bundle; import android.view.LayoutInflater; @@ -15,9 +15,9 @@ import java.util.List; import de.florianisme.wakeonlan.databinding.FragmentNetworkScanBinding; -import de.florianisme.wakeonlan.ui.home.list.LinearLayoutManagerWrapper; -import de.florianisme.wakeonlan.ui.home.scan.callbacks.ScanCallback; -import de.florianisme.wakeonlan.ui.home.scan.model.NetworkScanDevice; +import de.florianisme.wakeonlan.ui.list.layoutmanager.LinearLayoutManagerWrapper; +import de.florianisme.wakeonlan.ui.scan.callbacks.ScanCallback; +import de.florianisme.wakeonlan.ui.scan.model.NetworkScanDevice; public class NetworkScanFragment extends Fragment { diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/NetworkScanTask.java b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/NetworkScanTask.java similarity index 95% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/NetworkScanTask.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/scan/NetworkScanTask.java index 6825e2e..c240d14 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/NetworkScanTask.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/NetworkScanTask.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.scan; +package de.florianisme.wakeonlan.ui.scan; import android.content.Context; import android.net.wifi.WifiInfo; @@ -10,7 +10,7 @@ import java.net.InetAddress; import de.florianisme.wakeonlan.R; -import de.florianisme.wakeonlan.ui.home.scan.callbacks.ScanCallback; +import de.florianisme.wakeonlan.ui.scan.callbacks.ScanCallback; public class NetworkScanTask implements Runnable { diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/callbacks/ScanCallback.java b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/callbacks/ScanCallback.java similarity index 73% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/callbacks/ScanCallback.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/scan/callbacks/ScanCallback.java index b507fe5..b958116 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/callbacks/ScanCallback.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/callbacks/ScanCallback.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.scan.callbacks; +package de.florianisme.wakeonlan.ui.scan.callbacks; public interface ScanCallback { diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/model/NetworkScanDevice.java b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/model/NetworkScanDevice.java similarity index 95% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/model/NetworkScanDevice.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/scan/model/NetworkScanDevice.java index 24834d5..4dc2e3e 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/model/NetworkScanDevice.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/model/NetworkScanDevice.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.scan.model; +package de.florianisme.wakeonlan.ui.scan.model; import java.util.Optional; diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/viewholder/EmptyViewHolder.java b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/viewholder/EmptyViewHolder.java similarity index 78% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/viewholder/EmptyViewHolder.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/scan/viewholder/EmptyViewHolder.java index ebfe1c7..b06fae7 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/viewholder/EmptyViewHolder.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/viewholder/EmptyViewHolder.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.scan.viewholder; +package de.florianisme.wakeonlan.ui.scan.viewholder; import android.view.View; diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/scan/viewholder/ListViewType.java b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/viewholder/ListViewType.java new file mode 100644 index 0000000..d0cd6c3 --- /dev/null +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/viewholder/ListViewType.java @@ -0,0 +1,5 @@ +package de.florianisme.wakeonlan.ui.scan.viewholder; + +public enum ListViewType { + EMPTY, SCAN_DEVICE +} diff --git a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/viewholder/ScanResultViewHolder.java b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/viewholder/ScanResultViewHolder.java similarity index 91% rename from app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/viewholder/ScanResultViewHolder.java rename to app/src/main/java/de/florianisme/wakeonlan/ui/scan/viewholder/ScanResultViewHolder.java index fad29fe..0500641 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/ui/home/scan/viewholder/ScanResultViewHolder.java +++ b/app/src/main/java/de/florianisme/wakeonlan/ui/scan/viewholder/ScanResultViewHolder.java @@ -1,4 +1,4 @@ -package de.florianisme.wakeonlan.ui.home.scan.viewholder; +package de.florianisme.wakeonlan.ui.scan.viewholder; import android.content.Context; import android.content.Intent; @@ -13,8 +13,8 @@ import java.util.Optional; import de.florianisme.wakeonlan.R; -import de.florianisme.wakeonlan.ui.home.details.AddNetworkScanDeviceActivity; -import de.florianisme.wakeonlan.ui.home.scan.model.NetworkScanDevice; +import de.florianisme.wakeonlan.ui.modify.AddNetworkScanDeviceActivity; +import de.florianisme.wakeonlan.ui.scan.model.NetworkScanDevice; public class ScanResultViewHolder extends RecyclerView.ViewHolder { diff --git a/app/src/main/java/de/florianisme/wakeonlan/wear/WearClient.java b/app/src/main/java/de/florianisme/wakeonlan/wear/WearClient.java index 02340f1..f8fd546 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/wear/WearClient.java +++ b/app/src/main/java/de/florianisme/wakeonlan/wear/WearClient.java @@ -1,48 +1,47 @@ package de.florianisme.wakeonlan.wear; import android.content.Context; +import android.util.Log; -import androidx.annotation.NonNull; - +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.android.gms.wearable.DataClient; -import com.google.android.gms.wearable.DataEventBuffer; import com.google.android.gms.wearable.PutDataMapRequest; import com.google.android.gms.wearable.PutDataRequest; import com.google.android.gms.wearable.Wearable; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import de.florianisme.wakeonlan.models.DeviceDto; import de.florianisme.wakeonlan.persistence.entities.Device; -public class WearClient implements DataClient.OnDataChangedListener { +public class WearClient { - private final String DEVICE_LIST_PATH = "/device_list"; - private DataClient dataClient; + private static final String DEVICE_LIST_PATH = "/device_list"; + private final DataClient dataClient; - public WearClient init(Context context) { - Wearable.getDataClient(context).addListener(this); + public WearClient(Context context) { dataClient = Wearable.getDataClient(context); - - return this; } public void onDeviceListUpdated(List deviceList) { PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(DEVICE_LIST_PATH); - putDataMapRequest.getDataMap().putIntegerArrayList("deviceIds", (ArrayList) deviceList.stream().map(device -> device.id).collect(Collectors.toList())); - putDataMapRequest.getDataMap().putStringArrayList("deviceNames", (ArrayList) deviceList.stream().map(device -> device.name).collect(Collectors.toList())); + putDataMapRequest.getDataMap().putByteArray("devices", buildDevicesListByteArray(deviceList)); PutDataRequest putDataReq = putDataMapRequest.asPutDataRequest(); dataClient.putDataItem(putDataReq); } - public void destroy() { - dataClient.removeListener(this); - } - - @Override - public void onDataChanged(@NonNull DataEventBuffer dataEventBuffer) { - + private byte[] buildDevicesListByteArray(List devices) { + try { + List deviceDtos = devices.stream() + .map(device -> new DeviceDto(device.id, device.name)) + .collect(Collectors.toList()); + return new ObjectMapper().writeValueAsBytes(deviceDtos); + } catch (JsonProcessingException e) { + Log.e(getClass().getSimpleName(), "Could not transform list of devices to byte array", e); + return new byte[0]; + } } } diff --git a/app/src/main/java/de/florianisme/wakeonlan/wol/PacketBuilder.java b/app/src/main/java/de/florianisme/wakeonlan/wol/PacketBuilder.java index f7830c9..15b5e7c 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/wol/PacketBuilder.java +++ b/app/src/main/java/de/florianisme/wakeonlan/wol/PacketBuilder.java @@ -1,26 +1,69 @@ package de.florianisme.wakeonlan.wol; +import androidx.annotation.Nullable; + +import com.google.common.base.Strings; + import java.net.DatagramPacket; import java.net.InetAddress; import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.util.regex.Pattern; class PacketBuilder { - static DatagramPacket buildMagicPacket(String broadcastAddress, String macAddress, int port) throws UnknownHostException { + static DatagramPacket buildMagicPacket(String broadcastAddress, String macAddress, int port, @Nullable String secureOnPassword) throws UnknownHostException { byte[] macBytes = getMacBytes(macAddress); - byte[] bytes = new byte[6 + 16 * macBytes.length]; + byte[] secureOnPasswordBytes = getSecureOnPasswordBytes(secureOnPassword); + + // Packet is 6 times 0xff, 16 times MAC Address of target and 0, 4 or 6 character password + byte[] bytes = new byte[6 + (16 * macBytes.length) + secureOnPasswordBytes.length]; + + // Append 6 times 0xff for (int i = 0; i < 6; i++) { bytes[i] = (byte) 0xff; } - for (int i = 6; i < bytes.length; i += macBytes.length) { - System.arraycopy(macBytes, 0, bytes, i, macBytes.length); + + // Append MAC address 16 times + for (int i = 0; i < 16; i++) { + appendMacAddress(macBytes, bytes, i); } + // Append Password + System.arraycopy(secureOnPasswordBytes, 0, bytes, bytes.length - secureOnPasswordBytes.length, secureOnPasswordBytes.length); + InetAddress address = InetAddress.getByName(broadcastAddress); return new DatagramPacket(bytes, bytes.length, address, port); } + private static void appendMacAddress(byte[] macBytes, byte[] bytes, int iteration) { + System.arraycopy(macBytes, 0, bytes, (iteration + 1) * 6, macBytes.length); + } + + private static byte[] getSecureOnPasswordBytes(String secureOnPassword) { + byte[] bytes = Strings.nullToEmpty(secureOnPassword).getBytes(StandardCharsets.US_ASCII); + if (bytes.length == 0) { + return new byte[0]; + } + + if (passwordIsIpAddress(secureOnPassword)) { + return getIpBytes(secureOnPassword); + } else if (passwordIsMacAddress(secureOnPassword)) { + return getMacBytes(secureOnPassword); + } else { + throw new IllegalArgumentException("Invalid SecureOn Password: Has " + bytes.length + " characters"); + } + } + + private static boolean passwordIsMacAddress(String password) { + return Pattern.compile("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$").matcher(password).matches(); + } + + private static boolean passwordIsIpAddress(String password) { + return Pattern.compile("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$").matcher(password).matches(); + } + private static byte[] getMacBytes(String macStr) throws IllegalArgumentException { byte[] bytes = new byte[6]; String[] hex = macStr.split("(\\:|\\-)"); @@ -37,4 +80,22 @@ private static byte[] getMacBytes(String macStr) throws IllegalArgumentException return bytes; } + private static byte[] getIpBytes(String ipString) throws IllegalArgumentException { + byte[] bytes = new byte[4]; + String[] ipOctets = ipString.split("(\\.|\\-)"); + if (ipOctets.length != 4) { + throw new IllegalArgumentException("Invalid IP address."); + } + try { + for (int i = 0; i < 4; i++) { + int ipOctetNumber = Integer.parseInt(ipOctets[i]); + String hexString = Integer.toHexString(ipOctetNumber); + bytes[i] = (byte) Integer.parseInt(hexString, 16); + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid hex digit in IP address."); + } + return bytes; + } + } diff --git a/app/src/main/java/de/florianisme/wakeonlan/wol/WolSender.java b/app/src/main/java/de/florianisme/wakeonlan/wol/WolSender.java index c08e265..6b56600 100644 --- a/app/src/main/java/de/florianisme/wakeonlan/wol/WolSender.java +++ b/app/src/main/java/de/florianisme/wakeonlan/wol/WolSender.java @@ -4,18 +4,22 @@ import java.net.DatagramPacket; import java.net.DatagramSocket; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import de.florianisme.wakeonlan.persistence.entities.Device; public class WolSender { + public static final Executor EXECUTOR = Executors.newSingleThreadExecutor(); + public static void sendWolPacket(Device device) { - Thread thread = new Thread(new Runnable() { + Runnable sendWolRunnable = new Runnable() { @Override public void run() { try { - DatagramPacket packet = PacketBuilder.buildMagicPacket(device.broadcast_address, device.macAddress, device.port); + DatagramPacket packet = PacketBuilder.buildMagicPacket(device.broadcastAddress, device.macAddress, device.port, device.secureOnPassword); DatagramSocket socket = new DatagramSocket(); socket.send(packet); socket.close(); @@ -23,11 +27,9 @@ public void run() { Log.e(this.getClass().getName(), "Error while sending magic packet: ", e); } } - }); - - thread.start(); - + }; + EXECUTOR.execute(sendWolRunnable); } } diff --git a/app/src/main/res/drawable/computer_illustration.xml b/app/src/main/res/drawable/computer_illustration.xml new file mode 100644 index 0000000..c6eae07 --- /dev/null +++ b/app/src/main/res/drawable/computer_illustration.xml @@ -0,0 +1,481 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_modify_device.xml b/app/src/main/res/layout/activity_modify_device.xml index 480bc6f..e7abc19 100644 --- a/app/src/main/res/layout/activity_modify_device.xml +++ b/app/src/main/res/layout/activity_modify_device.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".ui.home.details.ModifyDeviceActivity"> + tools:context=".ui.modify.ModifyDeviceActivity"> + + + app:layout_constraintTop_toBottomOf="@id/device_title_general"> + + + @@ -130,7 +152,7 @@ android:layout_marginEnd="24dp" android:hint="@string/add_device_port" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/device_broadcast_layout"> + app:layout_constraintTop_toBottomOf="@id/device_broadcast_layout"> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/device_list_empty.xml b/app/src/main/res/layout/device_list_empty.xml index 88411e7..5e1ee7f 100644 --- a/app/src/main/res/layout/device_list_empty.xml +++ b/app/src/main/res/layout/device_list_empty.xml @@ -4,24 +4,35 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + + app:layout_constraintTop_toBottomOf="@id/device_list_empty_illustration" /> + app:layout_constraintTop_toBottomOf="@id/device_list_empty_title" /> \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 14988c4..478e877 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -17,25 +17,25 @@ \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index e722797..453c803 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -14,15 +14,16 @@ Ungültige MAC Adresse Name darf nicht leer sein Ungültige IP Adresse - Bitte korrigieren Sie ihre Eingaben + Bitte korrigieren Sie Ihre Eingaben Broadcast automatisch ausfüllen Fehler beim Zugriff auf die Maschine Einschalten Keine Geräte eingerichtet Tippen Sie auf das \"+\" Icon um Ihr erstes Gerät hinzuzufügen + Computer Illustration Bearbeiten Gerät löschen - Wollen Sie diese Gerät wirklich löschen? + Möchten Sie dieses Gerät wirklich löschen? WOL Gerät 1 WOL Gerät 2 WOL Gerät 3 @@ -35,7 +36,7 @@ Speichern Exportieren/Importieren Geräteübersicht - Exportieren Sie ihre konfigurierten Geräte in eine JSON Datei oder importieren Sie einen bestehenden Datensatz + Exportieren Sie Ihre konfigurierten Geräte in eine JSON Datei oder importieren Sie einen bestehenden Datensatz Hinweis: Beim Importieren werden alle konfigurierten Geräte überschrieben Exportieren @@ -48,6 +49,11 @@ Gerät hinzufügen Keine Geräte gefunden Sind Sie mit einem WLAN Netzwerk verbunden? - IP Addresse kann nicht abgefragt werden + IP Adresse kann nicht abgefragt werden Fehler beim Scannen des Netzwerks + Allgemein + Verbindung + Sicherheit + SecureOn Passwort (optional) + Passwort muss entweder leer oder eine gültige MAC oder IP Adresse sein \ No newline at end of file diff --git a/app/src/main/res/values-sw600dp/attrs.xml b/app/src/main/res/values-sw600dp/attrs.xml new file mode 100644 index 0000000..d3a0e92 --- /dev/null +++ b/app/src/main/res/values-sw600dp/attrs.xml @@ -0,0 +1,4 @@ + + + true + \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..f855de6 --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,4 @@ + + + false + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e45ccb9..6d91dbc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,12 +9,16 @@ Export/Import Data + General + Connection + Security Name Status IP (optional) MAC Address Port Broadcast Address Autofill Broadcast Address + SecureOn Password (optional) Save Delete @@ -25,6 +29,7 @@ Invalid MAC Address Name must not be empty Invalid IP Address + Password must be either empty, a valid MAC or IP Address Please correct your inputs first @@ -42,6 +47,7 @@ Turn on No devices set up Tap the \"+\" icon to add your first device + Computer Illustration Edit diff --git a/app/src/test/java/de/florianisme/wakeonlan/ExampleUnitTest.java b/app/src/test/java/de/florianisme/wakeonlan/ExampleUnitTest.java deleted file mode 100644 index aa52805..0000000 --- a/app/src/test/java/de/florianisme/wakeonlan/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package de.florianisme.wakeonlan; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -/** - * Example local unit test, which will execute on the development device (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/build.gradle b/build.gradle index ad7dc46..063716c 100644 --- a/build.gradle +++ b/build.gradle @@ -5,13 +5,24 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' + classpath 'com.android.tools.build:gradle:7.3.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } +allprojects { + repositories { + jcenter() + mavenCentral() + google() + flatDir { + dirs 'libs' + } + } +} + task clean(type: Delete) { delete rootProject.buildDir } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e52f091..0e2ce2e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Thu Dec 23 12:21:22 CET 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle b/settings.gradle index 50f1f54..cde6449 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ rootProject.name = "WakeOnLan" -include ':app', ':wear' +include ':app', ':wear', ':shared-models' \ No newline at end of file diff --git a/shared-build.gradle b/shared-build.gradle index 3be882e..9fdb679 100644 --- a/shared-build.gradle +++ b/shared-build.gradle @@ -1,6 +1,6 @@ android { namespace 'de.florianisme.wakeonlan' - compileSdk 31 + compileSdk 32 signingConfigs { release { @@ -17,8 +17,8 @@ android { defaultConfig { applicationId "de.florianisme.wakeonlan" minSdk 24 - targetSdk 31 - versionName "1.4.2" + targetSdk 32 + versionName "1.6.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -40,20 +40,11 @@ android { } } -allprojects { - repositories { - jcenter() - mavenCentral() - google() - flatDir { - dirs 'libs' - } - } -} - dependencies { - implementation 'com.google.android.support:wearable:2.9.0' compileOnly 'com.google.android.wearable:wearable:2.9.0' - implementation 'com.google.android.gms:play-services-wearable:17.1.0' + implementation 'com.google.android.gms:play-services-wearable:18.0.0' + + implementation 'com.google.guava:guava:31.0.1-jre' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.3' } \ No newline at end of file diff --git a/shared-models/build.gradle b/shared-models/build.gradle new file mode 100644 index 0000000..3697e34 --- /dev/null +++ b/shared-models/build.gradle @@ -0,0 +1,10 @@ +apply plugin: 'java' + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +dependencies { + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.13.3' +} \ No newline at end of file diff --git a/shared-models/src/main/java/de/florianisme/wakeonlan/models/DeviceDto.java b/shared-models/src/main/java/de/florianisme/wakeonlan/models/DeviceDto.java new file mode 100644 index 0000000..93b6d12 --- /dev/null +++ b/shared-models/src/main/java/de/florianisme/wakeonlan/models/DeviceDto.java @@ -0,0 +1,25 @@ +package de.florianisme.wakeonlan.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class DeviceDto { + + @JsonProperty("id") + private final int id; + + @JsonProperty("name") + private final String name; + + public DeviceDto(@JsonProperty("id") int id, @JsonProperty("name") String name) { + this.id = id; + this.name = name; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } +} diff --git a/wear/build.gradle b/wear/build.gradle index f6fc745..f7ff163 100644 --- a/wear/build.gradle +++ b/wear/build.gradle @@ -3,7 +3,7 @@ apply from: "$rootProject.projectDir/shared-build.gradle" android { defaultConfig { - versionCode 34 + versionCode 44 } } @@ -11,6 +11,7 @@ dependencies { implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' - implementation 'com.google.guava:guava:31.0.1-jre' implementation 'androidx.wear:wear:1.2.0' + + implementation project(path: ':shared-models') } \ No newline at end of file diff --git a/wear/proguard-rules.pro b/wear/proguard-rules.pro index 8a0dc3d..3e8f971 100644 --- a/wear/proguard-rules.pro +++ b/wear/proguard-rules.pro @@ -18,4 +18,13 @@ # If you keep the line number information, uncomment this to # hide the original source file name. --renamesourcefileattribute SourceFile \ No newline at end of file +-renamesourcefileattribute SourceFile + +# Jackson +-keep @com.fasterxml.jackson.annotation.JsonIgnoreProperties class * { *; } +-keep class com.fasterxml.** { *; } +-keep class org.codehaus.** { *; } +-keepnames class com.fasterxml.jackson.** { *; } +-keepclassmembers public final enum com.fasterxml.jackson.annotation.JsonAutoDetect$Visibility { + public static final com.fasterxml.jackson.annotation.JsonAutoDetect$Visibility *; +} diff --git a/wear/src/main/java/de/florianisme/wakeonlan/DeviceListActivity.java b/wear/src/main/java/de/florianisme/wakeonlan/DeviceListActivity.java index cf0ff20..c16f8e3 100644 --- a/wear/src/main/java/de/florianisme/wakeonlan/DeviceListActivity.java +++ b/wear/src/main/java/de/florianisme/wakeonlan/DeviceListActivity.java @@ -27,7 +27,7 @@ import de.florianisme.wakeonlan.mobile.DeviceQueryException; import de.florianisme.wakeonlan.mobile.MobileClient; import de.florianisme.wakeonlan.mobile.OnDataReceivedListener; -import de.florianisme.wakeonlan.model.Device; +import de.florianisme.wakeonlan.models.DeviceDto; public class DeviceListActivity extends Activity implements DataClient.OnDataChangedListener, OnDataReceivedListener { @@ -61,7 +61,7 @@ protected void onCreate(Bundle savedInstanceState) { MobileClient.getDevicesList(nodeClient, dataClient, this); } - private void updateRecyclerviewDataset(List devices) { + private void updateRecyclerviewDataset(List devices) { wearDeviceListAdapter.updateDataset(devices); wearDeviceListAdapter.notifyDataSetChanged(); } @@ -74,7 +74,7 @@ public void onDataChanged(@NonNull DataEventBuffer dataEventBuffer) { DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap(); try { - List devices = MobileClient.buildDeviceList(dataMap); + List devices = MobileClient.buildDeviceList(dataMap); onDataReceived(devices); } catch (DeviceQueryException e) { onError(e); @@ -90,7 +90,7 @@ protected void onDestroy() { } @Override - public void onDataReceived(List devices) { + public void onDataReceived(List devices) { updateRecyclerviewDataset(devices); } diff --git a/wear/src/main/java/de/florianisme/wakeonlan/list/OnDeviceClickedListener.java b/wear/src/main/java/de/florianisme/wakeonlan/list/OnDeviceClickedListener.java index c4aaec7..93211d6 100644 --- a/wear/src/main/java/de/florianisme/wakeonlan/list/OnDeviceClickedListener.java +++ b/wear/src/main/java/de/florianisme/wakeonlan/list/OnDeviceClickedListener.java @@ -1,8 +1,8 @@ package de.florianisme.wakeonlan.list; -import de.florianisme.wakeonlan.model.Device; +import de.florianisme.wakeonlan.models.DeviceDto; public interface OnDeviceClickedListener { - void onDeviceClicked(Device device); + void onDeviceClicked(DeviceDto device); } diff --git a/wear/src/main/java/de/florianisme/wakeonlan/list/WearDeviceListAdapter.java b/wear/src/main/java/de/florianisme/wakeonlan/list/WearDeviceListAdapter.java index 84eb68a..f913bb3 100644 --- a/wear/src/main/java/de/florianisme/wakeonlan/list/WearDeviceListAdapter.java +++ b/wear/src/main/java/de/florianisme/wakeonlan/list/WearDeviceListAdapter.java @@ -16,11 +16,11 @@ import de.florianisme.wakeonlan.list.viewholder.ListViewType; import de.florianisme.wakeonlan.list.viewholder.TitleViewHolder; import de.florianisme.wakeonlan.list.viewholder.WearDeviceItemViewHolder; -import de.florianisme.wakeonlan.model.Device; +import de.florianisme.wakeonlan.models.DeviceDto; public class WearDeviceListAdapter extends RecyclerView.Adapter { - private List devices = new ArrayList<>(); + private List devices = new ArrayList<>(); private final OnDeviceClickedListener onDeviceClickedListener; public WearDeviceListAdapter(OnDeviceClickedListener onDeviceClickedListener) { @@ -50,7 +50,7 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, return viewHolder; } - public void updateDataset(List devices) { + public void updateDataset(List devices) { this.devices = Collections.unmodifiableList(devices); } @@ -77,9 +77,9 @@ public int getItemCount() { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, final int position) { if (isActualDeviceViewHolder(position)) { WearDeviceItemViewHolder wearDeviceItemViewHolder = (WearDeviceItemViewHolder) viewHolder; - Device device = devices.get(position - 1); + DeviceDto device = devices.get(position - 1); - wearDeviceItemViewHolder.setDeviceName(device.name); + wearDeviceItemViewHolder.setDeviceName(device.getName()); wearDeviceItemViewHolder.setOnClickHandler(device, onDeviceClickedListener); } } diff --git a/wear/src/main/java/de/florianisme/wakeonlan/list/viewholder/WearDeviceItemViewHolder.java b/wear/src/main/java/de/florianisme/wakeonlan/list/viewholder/WearDeviceItemViewHolder.java index db0267f..5438b81 100644 --- a/wear/src/main/java/de/florianisme/wakeonlan/list/viewholder/WearDeviceItemViewHolder.java +++ b/wear/src/main/java/de/florianisme/wakeonlan/list/viewholder/WearDeviceItemViewHolder.java @@ -8,7 +8,7 @@ import de.florianisme.wakeonlan.R; import de.florianisme.wakeonlan.list.OnDeviceClickedListener; -import de.florianisme.wakeonlan.model.Device; +import de.florianisme.wakeonlan.models.DeviceDto; public class WearDeviceItemViewHolder extends RecyclerView.ViewHolder { @@ -23,7 +23,7 @@ public void setDeviceName(String name) { deviceButton.setText(name); } - public void setOnClickHandler(Device device, OnDeviceClickedListener onDeviceClickedListener) { + public void setOnClickHandler(DeviceDto device, OnDeviceClickedListener onDeviceClickedListener) { deviceButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { diff --git a/wear/src/main/java/de/florianisme/wakeonlan/mobile/DeviceQueryException.java b/wear/src/main/java/de/florianisme/wakeonlan/mobile/DeviceQueryException.java index 7e0de3a..a434fb7 100644 --- a/wear/src/main/java/de/florianisme/wakeonlan/mobile/DeviceQueryException.java +++ b/wear/src/main/java/de/florianisme/wakeonlan/mobile/DeviceQueryException.java @@ -4,4 +4,8 @@ public class DeviceQueryException extends Exception { public DeviceQueryException(String message) { super(message); } + + public DeviceQueryException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/wear/src/main/java/de/florianisme/wakeonlan/mobile/MobileClient.java b/wear/src/main/java/de/florianisme/wakeonlan/mobile/MobileClient.java index 6d666a7..04638c9 100644 --- a/wear/src/main/java/de/florianisme/wakeonlan/mobile/MobileClient.java +++ b/wear/src/main/java/de/florianisme/wakeonlan/mobile/MobileClient.java @@ -2,6 +2,7 @@ import android.net.Uri; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.android.gms.tasks.OnSuccessListener; import com.google.android.gms.wearable.DataClient; import com.google.android.gms.wearable.DataItem; @@ -13,10 +14,11 @@ import com.google.android.gms.wearable.NodeClient; import com.google.android.gms.wearable.PutDataRequest; -import java.util.ArrayList; +import java.io.IOException; +import java.util.Arrays; import java.util.List; -import de.florianisme.wakeonlan.model.Device; +import de.florianisme.wakeonlan.models.DeviceDto; public class MobileClient { @@ -32,12 +34,12 @@ public void onSuccess(List nodes) { }); } - public static void sendDeviceClickedMessage(NodeClient nodeClient, MessageClient messageClient, Device device) { + public static void sendDeviceClickedMessage(NodeClient nodeClient, MessageClient messageClient, DeviceDto device) { nodeClient.getConnectedNodes().addOnSuccessListener(new OnSuccessListener>() { @Override public void onSuccess(List nodes) { for (Node node : nodes) { - messageClient.sendMessage(node.getId(), DEVICE_CLICKED_PATH, new byte[]{(byte) device.id}); + messageClient.sendMessage(node.getId(), DEVICE_CLICKED_PATH, new byte[]{(byte) device.getId()}); } } }); @@ -56,7 +58,7 @@ public void onSuccess(DataItemBuffer dataItems) { for (DataItem item : dataItems) { DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap(); try { - List devices = buildDeviceList(dataMap); + List devices = buildDeviceList(dataMap); onDataReceivedListener.onDataReceived(devices); } catch (DeviceQueryException e) { onDataReceivedListener.onError(e); @@ -67,23 +69,18 @@ public void onSuccess(DataItemBuffer dataItems) { }); } - public static List buildDeviceList(DataMap dataMap) throws DeviceQueryException { - List devices = new ArrayList<>(); - ArrayList deviceIds = dataMap.getIntegerArrayList("deviceIds"); - ArrayList deviceNames = dataMap.getStringArrayList("deviceNames"); + public static List buildDeviceList(DataMap dataMap) throws DeviceQueryException { + byte[] deviceListBytes = dataMap.getByteArray("devices"); - if (deviceIds == null || deviceNames == null) { - throw new DeviceQueryException("deviceIds or deviceNames not existing"); + if (deviceListBytes == null) { + throw new DeviceQueryException("No bytes received"); } - for (int i = 0; i < deviceIds.size(); i++) { - Integer deviceId = deviceIds.get(i); - String deviceName = deviceNames.get(i); - - devices.add(new Device(deviceId, deviceName)); + try { + return Arrays.asList(new ObjectMapper().readValue(deviceListBytes, DeviceDto[].class)); + } catch (IOException e) { + throw new DeviceQueryException("Devices can not be parsed", e); } - - return devices; } } diff --git a/wear/src/main/java/de/florianisme/wakeonlan/mobile/OnDataReceivedListener.java b/wear/src/main/java/de/florianisme/wakeonlan/mobile/OnDataReceivedListener.java index d30971f..71bd488 100644 --- a/wear/src/main/java/de/florianisme/wakeonlan/mobile/OnDataReceivedListener.java +++ b/wear/src/main/java/de/florianisme/wakeonlan/mobile/OnDataReceivedListener.java @@ -2,11 +2,11 @@ import java.util.List; -import de.florianisme.wakeonlan.model.Device; +import de.florianisme.wakeonlan.models.DeviceDto; public interface OnDataReceivedListener { - void onDataReceived(List devices); + void onDataReceived(List devices); void onError(Exception e); } diff --git a/wear/src/main/java/de/florianisme/wakeonlan/model/Device.java b/wear/src/main/java/de/florianisme/wakeonlan/model/Device.java deleted file mode 100644 index f38b314..0000000 --- a/wear/src/main/java/de/florianisme/wakeonlan/model/Device.java +++ /dev/null @@ -1,12 +0,0 @@ -package de.florianisme.wakeonlan.model; - -public class Device { - - public Device(int id, String name) { - this.id = id; - this.name = name; - } - - public int id; - public String name; -}