android/ fest ins Repo aufgenommen — Build wie beim Adressmanager [apk]
All checks were successful
Build APK / build-apk (push) Successful in 5m25s
Der android-Ordner wurde bisher bei JEDEM CI-Lauf neu erzeugt (cap add android) und Kotlin/MainActivity/Plugin jedes Mal frisch reingepatcht — genau das war die Ursache der ganzen Kotlin-Build-Fehler. Jetzt liegt android/ einmal sauber eingerichtet im Repo: - kotlin-android-Plugin aktiviert (1.9.24, jvmTarget 17) - NetDiagScannerPlugin.kt + Snmp.kt + MainActivity.kt (registriert das Plugin) - AndroidManifest mit allen noetigen Permissions - Signing-Config in app/build.gradle (Werte aus gradle.properties) build.yml entschlackt: nur noch npm install, vite build, cap sync, gradle. Build-Artefakte bleiben per .gitignore aussen vor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
@ -30,94 +30,27 @@ jobs:
|
||||||
- name: Frontend bauen
|
- name: Frontend bauen
|
||||||
run: VITE_APP_VERSION="$(cat /tmp/BUILD_VERSION)" npx vite build
|
run: VITE_APP_VERSION="$(cat /tmp/BUILD_VERSION)" npx vite build
|
||||||
|
|
||||||
- name: Android-Projekt anlegen
|
# android/ liegt komplett im Repo (Kotlin, Plugin, MainActivity, Manifest,
|
||||||
run: |
|
# Signing-Config sind dort fest eingerichtet). cap sync kopiert nur die
|
||||||
# cap add android nur wenn android/ noch nicht vorhanden
|
# Web-Assets rein und aktualisiert die Plugin-Liste.
|
||||||
[ -d android ] || npx cap add android
|
|
||||||
|
|
||||||
- name: Natives Plugin kopieren
|
|
||||||
run: |
|
|
||||||
PLUGIN_DST=android/app/src/main/java/de/data_it_solution/netdiag
|
|
||||||
mkdir -p "$PLUGIN_DST"
|
|
||||||
cp native-plugin/NetDiagScannerPlugin.kt "$PLUGIN_DST/"
|
|
||||||
cp native-plugin/Snmp.kt "$PLUGIN_DST/"
|
|
||||||
|
|
||||||
- name: Kotlin im Gradle-Build aktivieren
|
|
||||||
run: |
|
|
||||||
# Das native Plugin ist Kotlin. Das von `cap add android` erzeugte
|
|
||||||
# App-Modul hat aber KEIN Kotlin aktiviert. Ohne kotlin-android-Plugin
|
|
||||||
# werden .kt-Dateien beim Build stillschweigend ignoriert (kein Fehler)
|
|
||||||
# -> NetDiagScannerPlugin landet nie im APK -> "plugin not implemented".
|
|
||||||
KOTLIN_VERSION=1.9.24
|
|
||||||
# 1. Kotlin-Gradle-Plugin auf Projektebene verfügbar machen
|
|
||||||
if ! grep -q 'kotlin-gradle-plugin' android/build.gradle; then
|
|
||||||
sed -i "/com.android.tools.build:gradle/a\\ classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:${KOTLIN_VERSION}\"" android/build.gradle
|
|
||||||
fi
|
|
||||||
# 2. Plugin im App-Modul anwenden + JVM-Target auf 17 (Java-Target von Capacitor 6)
|
|
||||||
APP=android/app/build.gradle
|
|
||||||
if ! grep -q 'kotlin-android' "$APP"; then
|
|
||||||
sed -i "/apply plugin: 'com.android.application'/a apply plugin: 'kotlin-android'" "$APP"
|
|
||||||
sed -i "/^android {/a\\ kotlinOptions { jvmTarget = '17' }" "$APP"
|
|
||||||
fi
|
|
||||||
# 3. Coroutines-Abhängigkeit (vom Plugin genutzt)
|
|
||||||
if ! grep -q 'kotlinx-coroutines-android' "$APP"; then
|
|
||||||
sed -i '/dependencies {/a \ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0"' "$APP"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: MainActivity durch Kotlin-Variante ersetzen (Plugin registrieren)
|
|
||||||
run: |
|
|
||||||
PKG=android/app/src/main/java/de/data_it_solution/netdiag
|
|
||||||
# `cap add android` erzeugt MainActivity.java — die registriert das
|
|
||||||
# Plugin NICHT und würde mit unserer .kt-Klasse kollidieren (gleiche
|
|
||||||
# Klasse, gleiches Package). Also Java-Datei löschen, Kotlin schreiben.
|
|
||||||
rm -f "$PKG/MainActivity.java"
|
|
||||||
cat > "$PKG/MainActivity.kt" <<'KT'
|
|
||||||
package de.data_it_solution.netdiag
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import com.getcapacitor.BridgeActivity
|
|
||||||
|
|
||||||
class MainActivity : BridgeActivity() {
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
registerPlugin(NetDiagScannerPlugin::class.java)
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KT
|
|
||||||
|
|
||||||
- name: Capacitor sync
|
- name: Capacitor sync
|
||||||
run: npx cap sync android
|
run: npx cap sync android
|
||||||
|
|
||||||
- name: Keystore vorbereiten
|
- name: Keystore + Signing-Properties vorbereiten
|
||||||
run: |
|
run: |
|
||||||
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > /tmp/release.jks
|
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > /tmp/release.jks
|
||||||
cat > android/gradle.properties <<PROPS
|
cat >> android/gradle.properties <<PROPS
|
||||||
android.useAndroidX=true
|
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
|
||||||
RELEASE_STORE_FILE=/tmp/release.jks
|
RELEASE_STORE_FILE=/tmp/release.jks
|
||||||
RELEASE_STORE_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }}
|
RELEASE_STORE_PASSWORD=${{ secrets.KEYSTORE_PASSWORD }}
|
||||||
RELEASE_KEY_ALIAS=${{ secrets.KEY_ALIAS }}
|
RELEASE_KEY_ALIAS=${{ secrets.KEY_ALIAS }}
|
||||||
RELEASE_KEY_PASSWORD=${{ secrets.KEY_PASSWORD }}
|
RELEASE_KEY_PASSWORD=${{ secrets.KEY_PASSWORD }}
|
||||||
PROPS
|
PROPS
|
||||||
|
|
||||||
- name: Release-Signing in build.gradle eintragen
|
|
||||||
run: |
|
|
||||||
cd android/app
|
|
||||||
sed -i '/buildTypes {/i \
|
|
||||||
signingConfigs {\
|
|
||||||
release {\
|
|
||||||
storeFile file(RELEASE_STORE_FILE)\
|
|
||||||
storePassword RELEASE_STORE_PASSWORD\
|
|
||||||
keyAlias RELEASE_KEY_ALIAS\
|
|
||||||
keyPassword RELEASE_KEY_PASSWORD\
|
|
||||||
}\
|
|
||||||
}' build.gradle
|
|
||||||
sed -i 's/minifyEnabled false/minifyEnabled false\n signingConfig signingConfigs.release/' build.gradle
|
|
||||||
|
|
||||||
- name: APK bauen (Release)
|
- name: APK bauen (Release)
|
||||||
run: |
|
run: |
|
||||||
cd android
|
cd android
|
||||||
echo "sdk.dir=/opt/android-sdk" > local.properties
|
echo "sdk.dir=/opt/android-sdk" > local.properties
|
||||||
echo "org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m" >> gradle.properties
|
|
||||||
chmod +x gradlew
|
chmod +x gradlew
|
||||||
./gradlew assembleRelease --no-daemon
|
./gradlew assembleRelease --no-daemon
|
||||||
|
|
||||||
|
|
|
||||||
101
android/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
|
||||||
|
|
||||||
|
# Built application files
|
||||||
|
*.apk
|
||||||
|
*.aar
|
||||||
|
*.ap_
|
||||||
|
*.aab
|
||||||
|
|
||||||
|
# Files for the ART/Dalvik VM
|
||||||
|
*.dex
|
||||||
|
|
||||||
|
# Java class files
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
bin/
|
||||||
|
gen/
|
||||||
|
out/
|
||||||
|
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||||
|
# release/
|
||||||
|
|
||||||
|
# Gradle files
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Local configuration file (sdk path, etc)
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# Proguard folder generated by Eclipse
|
||||||
|
proguard/
|
||||||
|
|
||||||
|
# Log Files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Android Studio Navigation editor temp files
|
||||||
|
.navigation/
|
||||||
|
|
||||||
|
# Android Studio captures folder
|
||||||
|
captures/
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
*.iml
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/tasks.xml
|
||||||
|
.idea/gradle.xml
|
||||||
|
.idea/assetWizardSettings.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/libraries
|
||||||
|
# Android Studio 3 in .gitignore file.
|
||||||
|
.idea/caches
|
||||||
|
.idea/modules.xml
|
||||||
|
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||||
|
.idea/navEditor.xml
|
||||||
|
|
||||||
|
# Keystore files
|
||||||
|
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||||
|
#*.jks
|
||||||
|
#*.keystore
|
||||||
|
|
||||||
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx/
|
||||||
|
|
||||||
|
# Google Services (e.g. APIs or Firebase)
|
||||||
|
# google-services.json
|
||||||
|
|
||||||
|
# Freeline
|
||||||
|
freeline.py
|
||||||
|
freeline/
|
||||||
|
freeline_project_description.json
|
||||||
|
|
||||||
|
# fastlane
|
||||||
|
fastlane/report.xml
|
||||||
|
fastlane/Preview.html
|
||||||
|
fastlane/screenshots
|
||||||
|
fastlane/test_output
|
||||||
|
fastlane/readme.md
|
||||||
|
|
||||||
|
# Version control
|
||||||
|
vcs.xml
|
||||||
|
|
||||||
|
# lint
|
||||||
|
lint/intermediates/
|
||||||
|
lint/generated/
|
||||||
|
lint/outputs/
|
||||||
|
lint/tmp/
|
||||||
|
# lint/reports/
|
||||||
|
|
||||||
|
# Android Profiling
|
||||||
|
*.hprof
|
||||||
|
|
||||||
|
# Cordova plugins for Capacitor
|
||||||
|
capacitor-cordova-android-plugins
|
||||||
|
|
||||||
|
# Copied web assets
|
||||||
|
app/src/main/assets/public
|
||||||
|
|
||||||
|
# Generated Config files
|
||||||
|
app/src/main/assets/capacitor.config.json
|
||||||
|
app/src/main/assets/capacitor.plugins.json
|
||||||
|
app/src/main/res/xml/config.xml
|
||||||
2
android/app/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/build/*
|
||||||
|
!/build/.npmkeep
|
||||||
72
android/app/build.gradle
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace "de.data_it_solution.netdiag"
|
||||||
|
compileSdk rootProject.ext.compileSdkVersion
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "de.data_it_solution.netdiag"
|
||||||
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
aaptOptions {
|
||||||
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
|
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
|
||||||
|
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '17'
|
||||||
|
}
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
// Werte kommen aus gradle.properties — in der CI aus dem Keystore-Secret
|
||||||
|
// befuellt. Lokale Debug-Builds brauchen sie nicht.
|
||||||
|
if (project.hasProperty('RELEASE_STORE_FILE')) {
|
||||||
|
storeFile file(RELEASE_STORE_FILE)
|
||||||
|
storePassword RELEASE_STORE_PASSWORD
|
||||||
|
keyAlias RELEASE_KEY_ALIAS
|
||||||
|
keyPassword RELEASE_KEY_PASSWORD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
flatDir{
|
||||||
|
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
|
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||||
|
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
|
||||||
|
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
|
||||||
|
implementation project(':capacitor-android')
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0"
|
||||||
|
testImplementation "junit:junit:$junitVersion"
|
||||||
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||||
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
||||||
|
implementation project(':capacitor-cordova-android-plugins')
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: 'capacitor.build.gradle'
|
||||||
|
|
||||||
|
try {
|
||||||
|
def servicesJSON = file('google-services.json')
|
||||||
|
if (servicesJSON.text) {
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
|
||||||
|
}
|
||||||
24
android/app/capacitor.build.gradle
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||||
|
dependencies {
|
||||||
|
implementation project(':capacitor-community-sqlite')
|
||||||
|
implementation project(':capacitor-app')
|
||||||
|
implementation project(':capacitor-filesystem')
|
||||||
|
implementation project(':capacitor-network')
|
||||||
|
implementation project(':capacitor-preferences')
|
||||||
|
implementation project(':capacitor-share')
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (hasProperty('postBuildExtras')) {
|
||||||
|
postBuildExtras()
|
||||||
|
}
|
||||||
21
android/app/proguard-rules.pro
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.getcapacitor.myapp;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExampleInstrumentedTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void useAppContext() throws Exception {
|
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||||
|
|
||||||
|
assertEquals("com.getcapacitor.app", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
||||||
47
android/app/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/AppTheme">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:label="@string/title_activity_main"
|
||||||
|
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths"></meta-data>
|
||||||
|
</provider>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
<!-- Permissions -->
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
</manifest>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package de.data_it_solution.netdiag
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.getcapacitor.BridgeActivity
|
||||||
|
|
||||||
|
class MainActivity : BridgeActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
registerPlugin(NetDiagScannerPlugin::class.java)
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,551 @@
|
||||||
|
package de.data_it_solution.netdiag
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.wifi.WifiManager
|
||||||
|
import com.getcapacitor.JSArray
|
||||||
|
import com.getcapacitor.JSObject
|
||||||
|
import com.getcapacitor.Plugin
|
||||||
|
import com.getcapacitor.PluginCall
|
||||||
|
import com.getcapacitor.PluginMethod
|
||||||
|
import com.getcapacitor.annotation.CapacitorPlugin
|
||||||
|
import com.getcapacitor.annotation.Permission
|
||||||
|
import com.getcapacitor.annotation.PermissionCallback
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.BufferedReader
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileReader
|
||||||
|
import java.net.DatagramPacket
|
||||||
|
import java.net.DatagramSocket
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.Socket
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NetDiagScanner — natives Scan-Plugin der NetDiag-App.
|
||||||
|
*
|
||||||
|
* Der WebView kann keine Raw-Sockets/ICMP/ARP. Diese Klasse führt die
|
||||||
|
* eigentlichen Netzwerk-Messungen durch und wird vom TS-Wrapper
|
||||||
|
* (src/lib/scanner.ts) über `registerPlugin('NetDiagScanner')` angesprochen.
|
||||||
|
*
|
||||||
|
* Integration: Datei nach
|
||||||
|
* android/app/src/main/java/de/data_it_solution/netdiag/
|
||||||
|
* kopieren und in MainActivity registrieren:
|
||||||
|
* registerPlugin(NetDiagScannerPlugin::class.java)
|
||||||
|
*/
|
||||||
|
@CapacitorPlugin(
|
||||||
|
name = "NetDiagScanner",
|
||||||
|
permissions = [
|
||||||
|
Permission(alias = "location", strings = [Manifest.permission.ACCESS_FINE_LOCATION])
|
||||||
|
]
|
||||||
|
)
|
||||||
|
class NetDiagScannerPlugin : Plugin() {
|
||||||
|
|
||||||
|
private val io = CoroutineScope(Dispatchers.IO)
|
||||||
|
private val stressRuns = ConcurrentHashMap<String, StressRun>()
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
/* Subnetz / lokale Netzwerkinfo */
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun getLocalSubnet(call: PluginCall) {
|
||||||
|
io.launch {
|
||||||
|
try {
|
||||||
|
val wifi = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
||||||
|
@Suppress("DEPRECATION") val dhcp = wifi.dhcpInfo
|
||||||
|
val ipInt = dhcp?.ipAddress ?: 0
|
||||||
|
val gwInt = dhcp?.gateway ?: 0
|
||||||
|
val ip = if (ipInt != 0) intToIp(ipInt) else firstLocalIpv4()
|
||||||
|
val gateway = if (gwInt != 0) intToIp(gwInt) else ""
|
||||||
|
val base = ip.substringBeforeLast('.', "192.168.1")
|
||||||
|
resolve(call, JSObject()
|
||||||
|
.put("subnet", "$base.0/24")
|
||||||
|
.put("ip", ip)
|
||||||
|
.put("gateway", gateway))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
call.reject("getLocalSubnet: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
/* IP-Scan: Geräte im Subnetz finden */
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun ipScan(call: PluginCall) {
|
||||||
|
val subnet = call.getString("subnet") ?: return call.reject("subnet fehlt")
|
||||||
|
val base = subnet.substringBeforeLast('.', "192.168.1")
|
||||||
|
io.launch {
|
||||||
|
try {
|
||||||
|
// Parallel-Ping über das gesamte /24
|
||||||
|
val alive = withContext(Dispatchers.IO) {
|
||||||
|
(1..254).map { host ->
|
||||||
|
async {
|
||||||
|
val ip = "$base.$host"
|
||||||
|
if (InetAddress.getByName(ip).isReachable(350)) ip else null
|
||||||
|
}
|
||||||
|
}.awaitAll().filterNotNull()
|
||||||
|
}
|
||||||
|
val arp = readArpTable()
|
||||||
|
val devices = JSArray()
|
||||||
|
for (ip in alive) {
|
||||||
|
val dev = JSObject().put("ip", ip)
|
||||||
|
arp[ip]?.let { dev.put("mac", it).put("vendor", ouiVendor(it)) }
|
||||||
|
try {
|
||||||
|
val name = InetAddress.getByName(ip).canonicalHostName
|
||||||
|
if (name != ip) dev.put("hostname", name)
|
||||||
|
} catch (_: Exception) { }
|
||||||
|
devices.put(dev)
|
||||||
|
}
|
||||||
|
resolve(call, JSObject().put("devices", devices))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
call.reject("ipScan: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
/* Port-Scan */
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun portScan(call: PluginCall) {
|
||||||
|
val ip = call.getString("ip") ?: return call.reject("ip fehlt")
|
||||||
|
val portsArg = call.getArray("ports") ?: JSArray()
|
||||||
|
val ports = (0 until portsArg.length()).map { portsArg.getInt(it) }
|
||||||
|
io.launch {
|
||||||
|
try {
|
||||||
|
val open = withContext(Dispatchers.IO) {
|
||||||
|
ports.map { port ->
|
||||||
|
async {
|
||||||
|
try {
|
||||||
|
Socket().use { s ->
|
||||||
|
s.connect(InetSocketAddress(ip, port), 700)
|
||||||
|
}
|
||||||
|
port
|
||||||
|
} catch (_: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.awaitAll().filterNotNull()
|
||||||
|
}
|
||||||
|
val arr = JSArray()
|
||||||
|
for (p in open) arr.put(JSObject().put("port", p).put("service", serviceName(p)))
|
||||||
|
resolve(call, JSObject().put("open", arr))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
call.reject("portScan: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
/* Ping-Qualität */
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun pingQuality(call: PluginCall) {
|
||||||
|
val host = call.getString("host") ?: return call.reject("host fehlt")
|
||||||
|
val count = call.getInt("count") ?: 20
|
||||||
|
io.launch {
|
||||||
|
try {
|
||||||
|
resolve(call, measurePing(host, count))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
call.reject("pingQuality: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun measurePing(host: String, count: Int): JSObject {
|
||||||
|
val times = ArrayList<Double>()
|
||||||
|
val addr = InetAddress.getByName(host)
|
||||||
|
repeat(count) {
|
||||||
|
val t0 = System.nanoTime()
|
||||||
|
if (addr.isReachable(1000)) {
|
||||||
|
times.add((System.nanoTime() - t0) / 1_000_000.0)
|
||||||
|
}
|
||||||
|
Thread.sleep(200)
|
||||||
|
}
|
||||||
|
val received = times.size
|
||||||
|
val loss = ((count - received) * 100) / count
|
||||||
|
val min = times.minOrNull() ?: 0.0
|
||||||
|
val max = times.maxOrNull() ?: 0.0
|
||||||
|
val avg = if (times.isNotEmpty()) times.average() else 0.0
|
||||||
|
// Jitter = mittlere absolute Abweichung aufeinanderfolgender Werte
|
||||||
|
var jitter = 0.0
|
||||||
|
for (i in 1 until times.size) jitter += Math.abs(times[i] - times[i - 1])
|
||||||
|
if (times.size > 1) jitter /= (times.size - 1)
|
||||||
|
return JSObject()
|
||||||
|
.put("sent", count).put("received", received).put("lossPct", loss)
|
||||||
|
.put("minMs", round1(min)).put("avgMs", round1(avg))
|
||||||
|
.put("maxMs", round1(max)).put("jitterMs", round1(jitter))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
/* WLAN-Scan */
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun wifiScan(call: PluginCall) {
|
||||||
|
if (getPermissionState("location") != com.getcapacitor.PermissionState.GRANTED) {
|
||||||
|
requestPermissionForAlias("location", call, "wifiScanPermCallback")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
doWifiScan(call)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PermissionCallback
|
||||||
|
private fun wifiScanPermCallback(call: PluginCall) {
|
||||||
|
if (getPermissionState("location") == com.getcapacitor.PermissionState.GRANTED) {
|
||||||
|
doWifiScan(call)
|
||||||
|
} else {
|
||||||
|
call.reject("Standortberechtigung für WLAN-Scan abgelehnt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doWifiScan(call: PluginCall) {
|
||||||
|
try {
|
||||||
|
val wifi = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
||||||
|
val arr = JSArray()
|
||||||
|
for (r in wifi.scanResults) {
|
||||||
|
val freq = r.frequency
|
||||||
|
arr.put(JSObject()
|
||||||
|
.put("ssid", if (r.SSID.isNullOrEmpty()) "(versteckt)" else r.SSID)
|
||||||
|
.put("bssid", r.BSSID ?: "")
|
||||||
|
.put("channel", freqToChannel(freq))
|
||||||
|
.put("rssi", r.level)
|
||||||
|
.put("band", if (freq > 4000) "5 GHz" else "2.4 GHz"))
|
||||||
|
}
|
||||||
|
resolve(call, JSObject().put("networks", arr))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
call.reject("wifiScan: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
/* DHCP-Discover (Rogue-DHCP-Erkennung) */
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun dhcpDiscover(call: PluginCall) {
|
||||||
|
io.launch {
|
||||||
|
try {
|
||||||
|
val servers = discoverDhcpServers()
|
||||||
|
val arr = JSArray()
|
||||||
|
val arp = readArpTable()
|
||||||
|
for (ip in servers) {
|
||||||
|
arr.put(JSObject().put("ip", ip).put("mac", arp[ip] ?: ""))
|
||||||
|
}
|
||||||
|
resolve(call, JSObject().put("servers", arr))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
call.reject("dhcpDiscover: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sendet einen DHCPDISCOVER-Broadcast und sammelt antwortende Server.
|
||||||
|
* Best-effort: scheitert auf manchen Geräten an Port-68-Belegung.
|
||||||
|
*/
|
||||||
|
private fun discoverDhcpServers(): List<String> {
|
||||||
|
val found = LinkedHashSet<String>()
|
||||||
|
val socket = DatagramSocket()
|
||||||
|
socket.broadcast = true
|
||||||
|
socket.soTimeout = 3000
|
||||||
|
try {
|
||||||
|
val xid = byteArrayOf(0x39, 0x03, 0x42.toByte(), 0x12)
|
||||||
|
val packet = buildDhcpDiscover(xid)
|
||||||
|
socket.send(DatagramPacket(packet, packet.size, InetAddress.getByName("255.255.255.255"), 67))
|
||||||
|
val buf = ByteArray(1500)
|
||||||
|
val deadline = System.currentTimeMillis() + 3000
|
||||||
|
while (System.currentTimeMillis() < deadline) {
|
||||||
|
try {
|
||||||
|
val resp = DatagramPacket(buf, buf.size)
|
||||||
|
socket.receive(resp)
|
||||||
|
// Server-Identifier (Option 54) aus dem Paket lesen, sonst Absender
|
||||||
|
val srv = parseDhcpServerId(buf, resp.length) ?: resp.address.hostAddress
|
||||||
|
if (srv != null) found.add(srv)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
socket.close()
|
||||||
|
}
|
||||||
|
return found.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
/* SNMP v2c GET (Switch: Link-Speed, Fehlerzähler) */
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun snmpGet(call: PluginCall) {
|
||||||
|
val host = call.getString("host") ?: return call.reject("host fehlt")
|
||||||
|
val community = call.getString("community") ?: "public"
|
||||||
|
val oidsArg = call.getArray("oids") ?: JSArray()
|
||||||
|
val oids = (0 until oidsArg.length()).map { oidsArg.getString(it) }
|
||||||
|
io.launch {
|
||||||
|
try {
|
||||||
|
val values = JSObject()
|
||||||
|
for (oid in oids) {
|
||||||
|
values.put(oid, Snmp.get(host, community, oid) ?: "-")
|
||||||
|
}
|
||||||
|
resolve(call, JSObject().put("values", values))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
call.reject("snmpGet: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
/* Traceroute (über das System-ping-Binary, kein Root nötig) */
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun traceroute(call: PluginCall) {
|
||||||
|
val host = call.getString("host") ?: return call.reject("host fehlt")
|
||||||
|
io.launch {
|
||||||
|
try {
|
||||||
|
val hops = JSArray()
|
||||||
|
for (ttl in 1..20) {
|
||||||
|
val hop = pingWithTtl(host, ttl)
|
||||||
|
hops.put(JSObject()
|
||||||
|
.put("ttl", ttl)
|
||||||
|
.put("ip", hop.ip)
|
||||||
|
.put("ms", hop.ms))
|
||||||
|
if (hop.ip == host || hop.reachedTarget) break
|
||||||
|
}
|
||||||
|
resolve(call, JSObject().put("hops", hops))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
call.reject("traceroute: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
/* Durchsatz-Test */
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun throughput(call: PluginCall) {
|
||||||
|
val host = call.getString("host") ?: return call.reject("host fehlt")
|
||||||
|
val port = call.getInt("port") ?: 5201
|
||||||
|
val durationSec = call.getInt("durationSec") ?: 10
|
||||||
|
io.launch {
|
||||||
|
try {
|
||||||
|
// Einfacher TCP-Durchsatz gegen eine Sink/Source-Gegenstelle:
|
||||||
|
// Download = empfangene Bytes, Upload = gesendete Bytes je Sekunde.
|
||||||
|
val res = measureThroughput(host, port, durationSec)
|
||||||
|
resolve(call, res)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
call.reject("throughput: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun measureThroughput(host: String, port: Int, durationSec: Int): JSObject {
|
||||||
|
val buf = ByteArray(64 * 1024)
|
||||||
|
var downBytes = 0L
|
||||||
|
var upBytes = 0L
|
||||||
|
// Upload-Phase
|
||||||
|
Socket().use { s ->
|
||||||
|
s.connect(InetSocketAddress(host, port), 3000)
|
||||||
|
val end = System.currentTimeMillis() + durationSec * 500L
|
||||||
|
val out = s.getOutputStream()
|
||||||
|
while (System.currentTimeMillis() < end) {
|
||||||
|
out.write(buf); upBytes += buf.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Download-Phase
|
||||||
|
Socket().use { s ->
|
||||||
|
s.connect(InetSocketAddress(host, port), 3000)
|
||||||
|
val end = System.currentTimeMillis() + durationSec * 500L
|
||||||
|
val inp = s.getInputStream()
|
||||||
|
while (System.currentTimeMillis() < end) {
|
||||||
|
val n = inp.read(buf); if (n < 0) break; downBytes += n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val secs = durationSec / 2.0
|
||||||
|
return JSObject()
|
||||||
|
.put("downMbps", round1(downBytes * 8.0 / 1_000_000.0 / secs))
|
||||||
|
.put("upMbps", round1(upBytes * 8.0 / 1_000_000.0 / secs))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
/* Dauer-/Stresstest */
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun startStressTest(call: PluginCall) {
|
||||||
|
val host = call.getString("host") ?: return call.reject("host fehlt")
|
||||||
|
val durationSec = call.getInt("durationSec") ?: 300
|
||||||
|
val runId = "run-${System.currentTimeMillis()}"
|
||||||
|
val run = StressRun(host, durationSec)
|
||||||
|
stressRuns[runId] = run
|
||||||
|
// Hinweis: für Läufe > einige Minuten sollte ein Foreground-Service
|
||||||
|
// gestartet werden, sonst kann Android den Prozess beenden.
|
||||||
|
io.launch {
|
||||||
|
val end = System.currentTimeMillis() + durationSec * 1000L
|
||||||
|
while (System.currentTimeMillis() < end && run.active) {
|
||||||
|
val q = measurePing(host, 5)
|
||||||
|
run.samples++
|
||||||
|
run.lossSum += q.getInteger("lossPct", 0) ?: 0
|
||||||
|
run.avgSum += q.getDouble("avgMs")
|
||||||
|
run.maxMs = Math.max(run.maxMs, q.getDouble("maxMs"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve(call, JSObject().put("runId", runId))
|
||||||
|
}
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun stopStressTest(call: PluginCall) {
|
||||||
|
val runId = call.getString("runId") ?: return call.reject("runId fehlt")
|
||||||
|
val run = stressRuns.remove(runId) ?: return call.reject("Lauf nicht gefunden")
|
||||||
|
run.active = false
|
||||||
|
val n = Math.max(1, run.samples)
|
||||||
|
resolve(call, JSObject()
|
||||||
|
.put("samples", run.samples)
|
||||||
|
.put("lossPct", run.lossSum / n)
|
||||||
|
.put("avgMs", round1(run.avgSum / n))
|
||||||
|
.put("maxMs", round1(run.maxMs)))
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StressRun(val host: String, val durationSec: Int) {
|
||||||
|
var active = true
|
||||||
|
var samples = 0
|
||||||
|
var lossSum = 0
|
||||||
|
var avgSum = 0.0
|
||||||
|
var maxMs = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
/* Hilfsfunktionen */
|
||||||
|
/* --------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
private fun resolve(call: PluginCall, data: JSObject) {
|
||||||
|
// Capacitor erwartet die Auflösung auf dem Main-Thread
|
||||||
|
activity.runOnUiThread { call.resolve(data) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun intToIp(i: Int): String =
|
||||||
|
"${i and 0xFF}.${i shr 8 and 0xFF}.${i shr 16 and 0xFF}.${i shr 24 and 0xFF}"
|
||||||
|
|
||||||
|
private fun firstLocalIpv4(): String {
|
||||||
|
java.net.NetworkInterface.getNetworkInterfaces().toList().forEach { ni ->
|
||||||
|
ni.inetAddresses.toList().forEach { addr ->
|
||||||
|
if (!addr.isLoopbackAddress && addr is java.net.Inet4Address) {
|
||||||
|
return addr.hostAddress ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "192.168.1.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
/** /proc/net/arp lesen -> Map IP -> MAC (kann auf neuen Android-Versionen leer sein) */
|
||||||
|
private fun readArpTable(): Map<String, String> {
|
||||||
|
val map = HashMap<String, String>()
|
||||||
|
try {
|
||||||
|
BufferedReader(FileReader(File("/proc/net/arp"))).use { br ->
|
||||||
|
br.readLine() // Kopfzeile
|
||||||
|
var line = br.readLine()
|
||||||
|
while (line != null) {
|
||||||
|
val parts = line.split(Regex("\\s+"))
|
||||||
|
if (parts.size >= 4 && parts[3] != "00:00:00:00:00:00") {
|
||||||
|
map[parts[0]] = parts[3].uppercase()
|
||||||
|
}
|
||||||
|
line = br.readLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_: Exception) { }
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ping mit fester TTL -> (antwortende IP, Latenz in ms) */
|
||||||
|
private data class Hop(val ip: String, val ms: Double, val reachedTarget: Boolean)
|
||||||
|
|
||||||
|
private fun pingWithTtl(host: String, ttl: Int): Hop {
|
||||||
|
return try {
|
||||||
|
val proc = ProcessBuilder("/system/bin/ping", "-c", "1", "-W", "2", "-t", ttl.toString(), host)
|
||||||
|
.redirectErrorStream(true).start()
|
||||||
|
val out = proc.inputStream.bufferedReader().readText()
|
||||||
|
proc.waitFor()
|
||||||
|
val ip = Regex("""From ([\d.]+)""").find(out)?.groupValues?.get(1)
|
||||||
|
?: Regex("""\((\d+\.\d+\.\d+\.\d+)\)""").find(out)?.groupValues?.get(1)
|
||||||
|
?: "*"
|
||||||
|
val ms = Regex("""time=([\d.]+)""").find(out)?.groupValues?.get(1)?.toDoubleOrNull() ?: 0.0
|
||||||
|
val reached = out.contains("bytes from")
|
||||||
|
Hop(if (reached) host else ip, ms, reached)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Hop("*", 0.0, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun freqToChannel(freq: Int): Int = when {
|
||||||
|
freq == 2484 -> 14
|
||||||
|
freq in 2412..2472 -> (freq - 2412) / 5 + 1
|
||||||
|
freq in 5170..5825 -> (freq - 5170) / 5 + 34
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun serviceName(port: Int): String = when (port) {
|
||||||
|
21 -> "ftp"; 22 -> "ssh"; 23 -> "telnet"; 53 -> "dns"; 80 -> "http"
|
||||||
|
139 -> "netbios"; 443 -> "https"; 445 -> "smb"; 502 -> "modbus"
|
||||||
|
1883 -> "mqtt"; 3389 -> "rdp"; 8080 -> "http-alt"; 8443 -> "https-alt"
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Minimaler OUI-Hersteller-Lookup. Für vollständige Abdeckung OUI-DB einbinden. */
|
||||||
|
private fun ouiVendor(mac: String): String {
|
||||||
|
val oui = mac.replace(":", "").take(6).uppercase()
|
||||||
|
return OUI[oui] ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun round1(v: Double): Double = Math.round(v * 10.0) / 10.0
|
||||||
|
|
||||||
|
private fun buildDhcpDiscover(xid: ByteArray): ByteArray {
|
||||||
|
val p = ByteArray(300)
|
||||||
|
p[0] = 1 // op = BOOTREQUEST
|
||||||
|
p[1] = 1 // htype = Ethernet
|
||||||
|
p[2] = 6 // hlen
|
||||||
|
System.arraycopy(xid, 0, p, 4, 4)
|
||||||
|
p[10] = 0x80.toByte() // Broadcast-Flag
|
||||||
|
// Magic Cookie
|
||||||
|
p[236] = 99; p[237] = 130.toByte(); p[238] = 83; p[239] = 99
|
||||||
|
// Option 53: DHCP Message Type = DISCOVER
|
||||||
|
p[240] = 53; p[241] = 1; p[242] = 1
|
||||||
|
p[243] = 255.toByte() // Ende
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseDhcpServerId(buf: ByteArray, len: Int): String? {
|
||||||
|
var i = 240
|
||||||
|
while (i + 1 < len) {
|
||||||
|
val opt = buf[i].toInt() and 0xFF
|
||||||
|
if (opt == 255) break
|
||||||
|
if (opt == 0) { i++; continue }
|
||||||
|
val l = buf[i + 1].toInt() and 0xFF
|
||||||
|
if (opt == 54 && l == 4) {
|
||||||
|
return "${buf[i+2].toInt() and 0xFF}.${buf[i+3].toInt() and 0xFF}." +
|
||||||
|
"${buf[i+4].toInt() and 0xFF}.${buf[i+5].toInt() and 0xFF}"
|
||||||
|
}
|
||||||
|
i += 2 + l
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** Kleiner OUI-Auszug — bei Bedarf vollständige IEEE-OUI-Datei einbinden. */
|
||||||
|
private val OUI = mapOf(
|
||||||
|
"3810D5" to "AVM", "DCA632" to "Raspberry Pi", "B827EB" to "Raspberry Pi",
|
||||||
|
"001CC0" to "Intel", "F0B479" to "Apple", "D8EB97" to "TP-Link"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
138
android/app/src/main/java/de/data_it_solution/netdiag/Snmp.kt
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
package de.data_it_solution.netdiag
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.net.DatagramPacket
|
||||||
|
import java.net.DatagramSocket
|
||||||
|
import java.net.InetAddress
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimaler SNMP-v2c-GET-Client.
|
||||||
|
*
|
||||||
|
* Reicht für das Auslesen einzelner OIDs (Link-Speed, Fehlerzähler) von
|
||||||
|
* gemanagten Switches. Implementiert nur so viel BER-Kodierung wie nötig.
|
||||||
|
*/
|
||||||
|
object Snmp {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eine OID per SNMP v2c GET abfragen.
|
||||||
|
*
|
||||||
|
* @return Wert als String oder null bei Fehler/Timeout
|
||||||
|
*/
|
||||||
|
fun get(host: String, community: String, oid: String): String? {
|
||||||
|
return try {
|
||||||
|
val request = buildGetRequest(community, oid)
|
||||||
|
val socket = DatagramSocket()
|
||||||
|
socket.soTimeout = 2500
|
||||||
|
socket.use {
|
||||||
|
it.send(DatagramPacket(request, request.size, InetAddress.getByName(host), 161))
|
||||||
|
val buf = ByteArray(2048)
|
||||||
|
val resp = DatagramPacket(buf, buf.size)
|
||||||
|
it.receive(resp)
|
||||||
|
parseFirstValue(buf, resp.length)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- BER-Kodierung ---- */
|
||||||
|
|
||||||
|
private fun tlv(tag: Int, value: ByteArray): ByteArray {
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
out.write(tag)
|
||||||
|
when {
|
||||||
|
value.size < 0x80 -> out.write(value.size)
|
||||||
|
value.size < 0x100 -> { out.write(0x81); out.write(value.size) }
|
||||||
|
else -> { out.write(0x82); out.write(value.size shr 8); out.write(value.size and 0xFF) }
|
||||||
|
}
|
||||||
|
out.write(value)
|
||||||
|
return out.toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun integer(v: Int): ByteArray {
|
||||||
|
val bytes = when {
|
||||||
|
v == 0 -> byteArrayOf(0)
|
||||||
|
v < 0x80 -> byteArrayOf(v.toByte())
|
||||||
|
v < 0x8000 -> byteArrayOf((v shr 8).toByte(), v.toByte())
|
||||||
|
else -> byteArrayOf((v shr 24).toByte(), (v shr 16).toByte(), (v shr 8).toByte(), v.toByte())
|
||||||
|
}
|
||||||
|
return tlv(0x02, bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun octetString(s: String): ByteArray = tlv(0x04, s.toByteArray())
|
||||||
|
|
||||||
|
private fun oid(o: String): ByteArray {
|
||||||
|
val parts = o.trim().trimStart('.').split('.').map { it.toInt() }
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
out.write(parts[0] * 40 + parts[1]) // erste zwei Subidentifier zusammengefasst
|
||||||
|
for (i in 2 until parts.size) {
|
||||||
|
var v = parts[i]
|
||||||
|
if (v < 0x80) {
|
||||||
|
out.write(v)
|
||||||
|
} else {
|
||||||
|
val stack = ArrayDeque<Int>()
|
||||||
|
stack.addFirst(v and 0x7F)
|
||||||
|
v = v shr 7
|
||||||
|
while (v > 0) { stack.addFirst((v and 0x7F) or 0x80); v = v shr 7 }
|
||||||
|
stack.forEach { out.write(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tlv(0x06, out.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildGetRequest(community: String, oidStr: String): ByteArray {
|
||||||
|
val varbind = tlv(0x30, oid(oidStr) + tlv(0x05, ByteArray(0))) // OID + NULL
|
||||||
|
val varbindList = tlv(0x30, varbind)
|
||||||
|
val requestId = (System.currentTimeMillis() and 0x7FFF).toInt()
|
||||||
|
val pdu = tlv(
|
||||||
|
0xA0, // GetRequest-PDU
|
||||||
|
integer(requestId) + integer(0) + integer(0) + varbindList,
|
||||||
|
)
|
||||||
|
val message = tlv(
|
||||||
|
0x30,
|
||||||
|
integer(1) + octetString(community) + pdu, // version 1 = SNMPv2c
|
||||||
|
)
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Antwort parsen: ersten Variablen-Wert herausziehen ---- */
|
||||||
|
|
||||||
|
private fun parseFirstValue(buf: ByteArray, len: Int): String? {
|
||||||
|
var i = 0
|
||||||
|
// Durch die Struktur navigieren bis zum ersten primitiven Wert nach einer OID
|
||||||
|
var lastWasOid = false
|
||||||
|
while (i < len) {
|
||||||
|
val tag = buf[i].toInt() and 0xFF
|
||||||
|
i++
|
||||||
|
if (i >= len) break
|
||||||
|
var l = buf[i].toInt() and 0xFF
|
||||||
|
i++
|
||||||
|
if (l and 0x80 != 0) {
|
||||||
|
val n = l and 0x7F
|
||||||
|
l = 0
|
||||||
|
for (k in 0 until n) { l = (l shl 8) or (buf[i].toInt() and 0xFF); i++ }
|
||||||
|
}
|
||||||
|
// Konstruierte Typen (SEQUENCE, PDU) aufsteigen
|
||||||
|
if (tag == 0x30 || tag == 0xA2 || tag == 0xA0) continue
|
||||||
|
if (tag == 0x06) { lastWasOid = true; i += l; continue }
|
||||||
|
if (lastWasOid) {
|
||||||
|
return when (tag) {
|
||||||
|
0x02, 0x41, 0x42, 0x43, 0x44, 0x46 -> { // INTEGER, Counter, Gauge, TimeTicks ...
|
||||||
|
var v = 0L
|
||||||
|
for (k in 0 until l) v = (v shl 8) or (buf[i + k].toLong() and 0xFF)
|
||||||
|
v.toString()
|
||||||
|
}
|
||||||
|
0x04 -> String(buf, i, l) // OCTET STRING
|
||||||
|
0x05 -> "" // NULL -> kein Wert
|
||||||
|
else -> {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
for (k in 0 until l) sb.append("%02X".format(buf[i + k]))
|
||||||
|
sb.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += l
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
android/app/src/main/res/drawable-land-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
android/app/src/main/res/drawable-land-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
android/app/src/main/res/drawable-land-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 9 KiB |
BIN
android/app/src/main/res/drawable-land-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
android/app/src/main/res/drawable-land-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
android/app/src/main/res/drawable-port-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
android/app/src/main/res/drawable-port-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 4 KiB |
BIN
android/app/src/main/res/drawable-port-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
android/app/src/main/res/drawable-port-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
android/app/src/main/res/drawable-port-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
|
@ -0,0 +1,34 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:viewportWidth="108">
|
||||||
|
<path
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:strokeWidth="1">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="78.5885"
|
||||||
|
android:endY="90.9159"
|
||||||
|
android:startX="48.7653"
|
||||||
|
android:startY="61.0927"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:strokeWidth="1" />
|
||||||
|
</vector>
|
||||||
170
android/app/src/main/res/drawable/ic_launcher_background.xml
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:viewportWidth="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#26A69A"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
</vector>
|
||||||
BIN
android/app/src/main/res/drawable/splash.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
12
android/app/src/main/res/layout/activity_main.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 15 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#FFFFFF</color>
|
||||||
|
</resources>
|
||||||
7
android/app/src/main/res/values/strings.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">NetDiag</string>
|
||||||
|
<string name="title_activity_main">NetDiag</string>
|
||||||
|
<string name="package_name">de.data_it_solution.netdiag</string>
|
||||||
|
<string name="custom_url_scheme">de.data_it_solution.netdiag</string>
|
||||||
|
</resources>
|
||||||
22
android/app/src/main/res/values/styles.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
|
<item name="android:background">@null</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
|
||||||
|
<item name="android:background">@drawable/splash</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
5
android/app/src/main/res/xml/file_paths.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<external-path name="my_images" path="." />
|
||||||
|
<cache-path name="my_cache_images" path="." />
|
||||||
|
</paths>
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.getcapacitor.myapp;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
public class ExampleUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addition_isCorrect() throws Exception {
|
||||||
|
assertEquals(4, 2 + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
android/build.gradle
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:8.2.1'
|
||||||
|
classpath 'com.google.gms:google-services:4.4.0'
|
||||||
|
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24'
|
||||||
|
|
||||||
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
// in the individual module build.gradle files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "variables.gradle"
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
||||||
21
android/capacitor.settings.gradle
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||||
|
include ':capacitor-android'
|
||||||
|
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
||||||
|
|
||||||
|
include ':capacitor-community-sqlite'
|
||||||
|
project(':capacitor-community-sqlite').projectDir = new File('../node_modules/@capacitor-community/sqlite/android')
|
||||||
|
|
||||||
|
include ':capacitor-app'
|
||||||
|
project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
|
||||||
|
|
||||||
|
include ':capacitor-filesystem'
|
||||||
|
project(':capacitor-filesystem').projectDir = new File('../node_modules/@capacitor/filesystem/android')
|
||||||
|
|
||||||
|
include ':capacitor-network'
|
||||||
|
project(':capacitor-network').projectDir = new File('../node_modules/@capacitor/network/android')
|
||||||
|
|
||||||
|
include ':capacitor-preferences'
|
||||||
|
project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
|
||||||
|
|
||||||
|
include ':capacitor-share'
|
||||||
|
project(':capacitor-share').projectDir = new File('../node_modules/@capacitor/share/android')
|
||||||
22
android/gradle.properties
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Project-wide Gradle settings.
|
||||||
|
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx1536m
|
||||||
|
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
|
||||||
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
|
# Android operating system, and which are packaged with your app's APK
|
||||||
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
|
android.useAndroidX=true
|
||||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
7
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
248
android/gradlew
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC3045
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command;
|
||||||
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
# double quotes to make sure that they get re-expanded; and
|
||||||
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
92
android/gradlew.bat
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%"=="" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
5
android/settings.gradle
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
include ':app'
|
||||||
|
include ':capacitor-cordova-android-plugins'
|
||||||
|
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
|
||||||
|
|
||||||
|
apply from: 'capacitor.settings.gradle'
|
||||||
16
android/variables.gradle
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
ext {
|
||||||
|
minSdkVersion = 22
|
||||||
|
compileSdkVersion = 34
|
||||||
|
targetSdkVersion = 34
|
||||||
|
androidxActivityVersion = '1.8.0'
|
||||||
|
androidxAppCompatVersion = '1.6.1'
|
||||||
|
androidxCoordinatorLayoutVersion = '1.2.0'
|
||||||
|
androidxCoreVersion = '1.12.0'
|
||||||
|
androidxFragmentVersion = '1.6.2'
|
||||||
|
coreSplashScreenVersion = '1.0.1'
|
||||||
|
androidxWebkitVersion = '1.9.0'
|
||||||
|
junitVersion = '4.13.2'
|
||||||
|
androidxJunitVersion = '1.1.5'
|
||||||
|
androidxEspressoCoreVersion = '3.5.1'
|
||||||
|
cordovaAndroidVersion = '10.1.1'
|
||||||
|
}
|
||||||