Compare commits

...
Sign in to create a new pull request.

38 commits
master ... cepa

Author SHA1 Message Date
Jérémy Munsch
a784f0e4f0
Upgrade and fixes
Some checks are pending
Android CI / build (push) Waiting to run
- Fixes #2 change default pwd
- Fixes #5 change kios info display layout, removes refresh button
- Fixes #4 make ping status more visible
- Fixes #3 Make actual wifi network visible on wifi list
- Fix EventObserver LifeCycle on KioskActivity
- update git ignore
- Change Json schema validate lib + resolve fucking pro guard rules with it
- Fix password validation if password is too weak on server

Increment version to 1.0.1
2023-09-14 14:10:04 +02:00
Vincent BRIDIER
4f12e5a8f6 Merge branch 'evo-jeremy' into 'cepa'
Some checks are pending
Android CI / build (push) Waiting to run
Evo jeremy

See merge request ldc-web/projets/ldc-tid-logistique!1
2023-08-18 09:20:17 +00:00
Vincent BRIDIER
53896d2865
fix information textview width + bump to 1.0.0 for release 2023-08-18 11:19:38 +02:00
Jérémy Munsch
478f6334ec
Move PWD sync to Home sync button + Fix VNC arg edit not saving in profile
Some checks are pending
Android CI / build (push) Waiting to run
2023-08-11 17:40:37 +02:00
Vincent BRIDIER
708bba6f59
comment fetch on password activity 2023-08-10 11:39:16 +02:00
Jérémy Munsch
ce5cc01ba5
remove default password set in EditText field 2023-08-09 12:57:59 +02:00
Jérémy Munsch
8fd5b838bb
bump version to 0.0.4 2023-08-08 15:12:50 +02:00
Jérémy Munsch
1329f78a8a
optimize imports 2023-08-08 15:08:48 +02:00
Jérémy Munsch
10234ccfa8
Wifi list max width 2023-08-08 15:07:21 +02:00
Jérémy Munsch
e6897c42ed
Precise percent on wifi strengh bar 2023-08-08 13:10:30 +02:00
Jérémy Munsch
b859623950
Fix persistence again 2023-08-08 13:07:50 +02:00
Jérémy Munsch
d2ee0352cc
Remove unused toast 2023-08-08 12:55:19 +02:00
Jérémy Munsch
ec25150632
Fix config persitance after fetching it 2023-08-08 12:54:49 +02:00
Jérémy Munsch
dceddbec3c
Fragment tab customize 2023-08-08 12:45:18 +02:00
Jérémy Munsch
d70f1e46ab
hide reload button on kiosk informations fragment 2023-08-08 12:03:55 +02:00
Jérémy Munsch
dd5b421d34
fix Vnc arg display 2023-08-08 12:00:33 +02:00
Jérémy Munsch
76b80c7f7d
fix Kiosk main activity fragment switch on scroll 2023-08-08 11:56:27 +02:00
Jérémy Munsch
97143ccf7a
scrollview fix for wifi list 2023-08-08 11:26:59 +02:00
Jérémy Munsch
edb556f2a3
fix for empty SSID 2023-08-08 11:18:47 +02:00
Jérémy Munsch
85a003c42d
Added more precise wifi mesure percent + BSSID if empty RSSID 2023-08-08 11:10:13 +02:00
Jérémy Munsch
8027dbd574
Fixed wifi signal percent + added signal frequency 2023-08-08 10:47:11 +02:00
Jérémy Munsch
5eead612ee
Fixed get wifi networks 2023-08-08 09:48:15 +02:00
Vincent BRIDIER
af8d8e2570
some updates 2023-08-07 15:12:52 +02:00
Jérémy Munsch
df62f17c7a
resize view elements 2023-07-12 18:22:36 +02:00
Jérémy Munsch
66a697904a
alph ok 2023-07-12 17:56:08 +02:00
Jérémy Munsch
2a77e0ab62
DiagnosticActivity hide reboot button 2023-07-11 17:11:34 +02:00
Jérémy Munsch
9037ce9bfb
min sdk version bump to 29 2023-07-11 16:36:53 +02:00
Jérémy Munsch
e5ae57a657
Add reboot function 2023-07-11 16:33:31 +02:00
Jérémy Munsch
cfd7b2fe9f
Ping activity ok 2023-07-11 16:20:14 +02:00
Jérémy Munsch
ebbbf468a5
Configuration changes and persistent data ok 2023-07-11 11:23:05 +02:00
Jérémy Munsch
d750be9ac8
Added Parameter Activity using password fetched form server 2023-07-10 15:53:25 +02:00
Jérémy Munsch
d4b64d5ae6
Info page ok for now 2023-07-07 12:10:24 +02:00
Jérémy Munsch
1085cacc35
First Step Migrate Main activity to fragments + small refactor 2023-07-05 17:22:45 +02:00
Vincent BRIDIER
fd99d655a0
Fix http + Add regex
Some checks are pending
Android CI / build (push) Waiting to run
2022-12-20 09:57:32 +01:00
Vincent BRIDIER
fd16bea84f
Add Serail Int Caster + change logo + handle http call errors + add csv library 2022-12-19 12:06:22 +01:00
Vincent BRIDIER
deeef24a25
Fix CSV parsing 2022-12-15 09:55:03 +01:00
Vincent BRIDIER
a8c216e8f5
Update Logo, name, version for release + network state watchers 2022-11-21 18:03:04 +01:00
Vincent BRIDIER
5abd9bb5c4
save cepa v0 tmp 2022-11-16 17:48:06 +01:00
90 changed files with 3472 additions and 71 deletions

7
.gitignore vendored
View file

@ -39,3 +39,10 @@ gastest.o
#Private data
private/
# Release APK
app/release/
# Proguard generated files
app/seeds.txt
app/usage.txt

View file

@ -4,6 +4,9 @@
<JavaCodeStyleSettings>
<option name="JD_P_AT_EMPTY_LINES" value="false" />
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="ObjectiveC">
<option name="RIGHT_MARGIN" value="120" />
<option name="SOFT_MARGINS" value="80,100" />
@ -121,9 +124,9 @@
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="RIGHT_MARGIN" value="120" />
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" />
<option name="CLASS_ANNOTATION_WRAP" value="1" />
<option name="WRAP_ON_TYPING" value="0" />
</codeStyleSettings>

View file

@ -5,14 +5,15 @@ apply plugin: 'kotlinx-serialization'
apply plugin: 'kotlin-parcelize'
android {
compileSdkVersion 30
compileSdkVersion 29
defaultConfig {
applicationId "com.gaurav.avnc"
minSdkVersion 21
targetSdkVersion 30
applicationId "com.ldc.kiosk"
minSdkVersion 29
targetSdkVersion 29
versionCode 14
versionName "1.9.2"
//versionName "1.9.2"
versionName "1.0.1"
javaCompileOptions {
annotationProcessorOptions {
@ -53,14 +54,32 @@ android {
buildFeatures {
dataBinding true
}
signingConfigs {
buildTypes {
debug {
buildConfigField "java.util.Date", "BUILD_TIME", "new java.util.Date(" + System.currentTimeMillis() + "L)"
}
release {
buildConfigField "java.util.Date", "BUILD_TIME", "new java.util.Date(" + System.currentTimeMillis() + "L)"
}
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
packagingOptions {
exclude 'META-INF/ASL-2.0.txt'
exclude 'META-INF/LGPL-3.0.txt'
}
externalNativeBuild {
cmake {
path file('CMakeLists.txt')
@ -79,19 +98,31 @@ android {
dependencies {
implementation "androidx.core:core-ktx:1.5.0"
implementation "androidx.activity:activity-ktx:1.2.3"
implementation "androidx.fragment:fragment-ktx:1.3.4"
implementation 'androidx.activity:activity-ktx:1.3.1'
implementation "androidx.fragment:fragment-ktx:1.4.1"
implementation "androidx.appcompat:appcompat:1.3.0"
implementation "androidx.dynamicanimation:dynamicanimation:1.0.0"
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha04"
implementation "androidx.preference:preference:1.1.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
implementation "org.greenrobot:eventbus:3.3.1"
implementation "androidx.recyclerview:recyclerview:1.2.0"
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation "androidx.drawerlayout:drawerlayout:1.1.1"
implementation "androidx.room:room-runtime:2.3.0"
implementation "androidx.room:room-ktx:2.3.0"
kapt "androidx.room:room-compiler:2.3.0"
implementation "androidx.room:room-runtime:2.4.0"
implementation "androidx.room:room-ktx:2.4.0"
kapt "androidx.room:room-compiler:2.4.0"
implementation files('libs/com.serialIntCaster-1.1.0.jar')
// Life-cycle
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
implementation "androidx.lifecycle:lifecycle-process:2.4.0"
implementation "androidx.lifecycle:lifecycle-common:2.4.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
// Json
implementation "com.networknt:json-schema-validator:1.0.70"
implementation "com.google.guava:guava:29.0-android"
implementation "com.google.android.material:material:1.3.0"
@ -108,4 +139,7 @@ dependencies {
androidTestImplementation "androidx.test:rules:1.4.0"
androidTestImplementation "androidx.test.ext:junit-ktx:1.1.3"
androidTestImplementation "io.mockk:mockk-android:1.12.0"
implementation("com.squareup.okhttp3:okhttp:4.10.0")
implementation("org.apache.commons:commons-csv:1.5")
}

Binary file not shown.

Binary file not shown.

View file

@ -1,5 +1,7 @@
-dontobfuscate
-keepattributes SourceFile,LineNumberTable
-printusage usage.txt
-printseeds seeds.txt
##########################################################################
# Rules for keeping JSON Serialization code
@ -21,4 +23,18 @@
}
-keepclasseswithmembers class com.gaurav.avnc.** {
kotlinx.serialization.KSerializer serializer(...);
}
# Jackson
-keepclassmembers class ** extends com.fasterxml.jackson.databind.ser.std.** {
public <init>(...);
}
-keepclassmembers class ** extends com.fasterxml.jackson.databind.deser.std.** {
public <init>(...);
}
# networknt
-keepclassmembers class com.networknt.schema.** {
public <init>(...);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View file

@ -1,16 +1,15 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2020 Gaurav Ujjwal.
~
~ SPDX-License-Identifier: GPL-3.0-or-later
~
~ See COPYING.txt for more details.
-->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.gaurav.avnc">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_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.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.REBOOT" />
<application
android:name=".App"
@ -18,25 +17,69 @@
android:fullBackupContent="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".ui.ldc.KioskPingActivity"
android:exported="false" />
<activity
android:name=".ui.ldc.KioskDiagnosticActivity"
android:exported="false" />
<activity
android:name=".ui.ldc.KioskParameterActivity"
android:exported="false" />
<activity
android:name=".ui.ldc.KioskParameterPasswordActivity"
android:exported="false" />
<activity
android:name=".ui.ldc.KioskActivity"
android:exported="true">
<meta-data
android:name="android.app.lib_name"
android:value="" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.home.SplashActivity"
android:theme="@style/SplashTheme" />
<activity-alias
android:name=".StartupActivity"
android:exported="true"
android:targetActivity=".ui.home.SplashActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- <intent-filter> -->
<!-- <action android:name="android.intent.action.MAIN" /> -->
<!-- <category android:name="android.intent.category.LAUNCHER" /> -->
<!-- </intent-filter> -->
</activity-alias>
<activity android:name=".ui.home.HomeActivity" />
<activity
android:name=".ui.home.UrlBarActivity"
android:parentActivityName=".ui.home.HomeActivity"
android:windowSoftInputMode="stateVisible" />
<activity
android:name=".ui.vnc.VncActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
tools:ignore="UnusedAttribute" />
<activity android:name=".ui.vnc.UriReceiverActivity" />
<activity-alias
android:name=".UriReceiverActivity"
android:exported="true"
android:targetActivity=".ui.vnc.UriReceiverActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@ -47,28 +90,7 @@
</intent-filter>
</activity-alias>
<activity
android:name=".ui.home.SplashActivity"
android:theme="@style/SplashTheme" />
<activity android:name=".ui.home.HomeActivity" />
<activity
android:name=".ui.home.UrlBarActivity"
android:parentActivityName=".ui.home.HomeActivity"
android:windowSoftInputMode="stateVisible" />
<activity
android:name=".ui.vnc.VncActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
tools:ignore="UnusedAttribute" />
<activity android:name=".ui.vnc.UriReceiverActivity" />
<activity android:name=".ui.prefs.PrefsActivity" />
<activity android:name=".ui.about.AboutActivity" />
</application>

View file

@ -0,0 +1,14 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Configuration",
"description": "Kiosk app configuration",
"type": "object",
"additionalProperties": false,
"properties": {
"password": {
"type": "string",
"description": "Kiosk config password"
}
},
"required": ["password"]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View file

@ -24,11 +24,13 @@ class App : Application() {
}
private fun updateNightMode(theme: String) {
val nightMode = when (theme) {
var nightMode = when (theme) {
"light" -> AppCompatDelegate.MODE_NIGHT_NO
"dark" -> AppCompatDelegate.MODE_NIGHT_YES
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
// * Force Light mode
nightMode = AppCompatDelegate.MODE_NIGHT_NO;
AppCompatDelegate.setDefaultNightMode(nightMode)
}
}

View file

@ -0,0 +1,196 @@
package com.gaurav.avnc.ui.ldc
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.Observer
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.asLiveData
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import androidx.viewpager2.widget.ViewPager2
import com.gaurav.avnc.R
import com.gaurav.avnc.ui.ldc.events.AppSwitchedToForegroundEvent
import com.gaurav.avnc.ui.ldc.events.NetworkStateChangedEvent
import com.gaurav.avnc.ui.ldc.lib.ApplicationInfo
import com.gaurav.avnc.ui.ldc.lib.KioskViewPagerAdapter
import com.gaurav.avnc.ui.ldc.lib.NetworkObserver
import com.google.android.material.tabs.TabLayout
import okhttp3.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class KioskActivity : AppCompatActivity(), LifecycleObserver {
// * Network
private val networkObserver = NetworkObserver(this, lifecycle)
init { lifecycle.addObserver(networkObserver) }
// * Tab Elements
private lateinit var tabLayout: TabLayout
private lateinit var viewPager: ViewPager2
private lateinit var kioskViewPagerAdapter: KioskViewPagerAdapter
// * AppInfo
lateinit var appInfo: ApplicationInfo
private set
// * Permissions
private lateinit var permissionLauncher: ActivityResultLauncher<Array<String>>
private var isInternetPermissionGranted = false
private var isNetworkStatePermissionGranted = false
private var isWifiStatePermissionGranted = false
/**
* On Create
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_kiosk)
// * Setup AppInfo
appInfo = ApplicationInfo(getDefaultSharedPreferences(applicationContext))
// * Request Permissions
permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
isInternetPermissionGranted = permissions[android.Manifest.permission.INTERNET] ?: isInternetPermissionGranted
isNetworkStatePermissionGranted = permissions[android.Manifest.permission.ACCESS_NETWORK_STATE] ?: isNetworkStatePermissionGranted
isWifiStatePermissionGranted = permissions[android.Manifest.permission.ACCESS_WIFI_STATE] ?: isWifiStatePermissionGranted
}
requestPermission()
// * Init tabs
tabLayout = findViewById(R.id.kiosk_tab_layout)
kioskViewPagerAdapter = KioskViewPagerAdapter(this)
viewPager = findViewById(R.id.kiosk_view_pager)
viewPager.adapter = kioskViewPagerAdapter
// * Tab Selection
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
viewPager.currentItem = tab.position
}
override fun onTabReselected(tab: TabLayout.Tab) {
viewPager.currentItem = tab.position
}
override fun onTabUnselected(tab: TabLayout.Tab) {}
})
viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
viewPager.currentItem = position
tabLayout.selectTab(tabLayout.getTabAt(position))
}
})
// * Register event bus
EventBus.getDefault().register(this)
// Check NetWork Connection
listenToNetworkConnectionChanges()
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}
/**
* Event on Activity Resume
*/
override fun onResume() {
super.onResume()
// * AppInfo Refresh
appInfo.refreshConfigProperties()
appInfo.retrieveDeviceNetworkInfo(applicationContext)
}
/**
* Event on App Foreground
*/
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onForegroundStart() {
EventBus.getDefault().post(AppSwitchedToForegroundEvent())
}
/**
* Event on App Kill
*/
override fun onDestroy() {
super.onDestroy()
// * Register event bus
EventBus.getDefault().unregister(this)
ProcessLifecycleOwner.get().lifecycle.removeObserver(this)
}
/**
* On network message received
*/
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: NetworkStateChangedEvent?) {
appInfo.networkConnected = networkObserver.isConnected
appInfo.retrieveDeviceNetworkInfo(applicationContext)
Log.d("network", "network state changed on kiosk activity")
// runOnUiThread {
// Toast.makeText(applicationContext, "Network changed", Toast.LENGTH_SHORT).show();
// }
}
/**
* Observe Network connection
*/
private fun listenToNetworkConnectionChanges() {
networkObserver.networkAvailableStateFlow.asLiveData().observe(this, Observer {
appInfo.networkConnected = networkObserver.isConnected
appInfo.retrieveDeviceNetworkInfo(applicationContext)
Log.d("network", "network state changed on kiosk activity")
// runOnUiThread {
// Toast.makeText(applicationContext, "Network changed", Toast.LENGTH_SHORT).show();
// }
EventBus.getDefault().post(NetworkStateChangedEvent())
})
}
/**
* Request application permissions
*/
private fun requestPermission() {
isInternetPermissionGranted = ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.INTERNET
) == PackageManager.PERMISSION_GRANTED
isNetworkStatePermissionGranted = ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.ACCESS_NETWORK_STATE
) == PackageManager.PERMISSION_GRANTED
isWifiStatePermissionGranted = ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.ACCESS_WIFI_STATE
) == PackageManager.PERMISSION_GRANTED
val permissionRequest: MutableList<String> = ArrayList()
if (!isInternetPermissionGranted) {
permissionRequest.add(android.Manifest.permission.INTERNET)
}
if (!isNetworkStatePermissionGranted) {
permissionRequest.add(android.Manifest.permission.ACCESS_NETWORK_STATE)
}
if (!isWifiStatePermissionGranted) {
permissionRequest.add(android.Manifest.permission.ACCESS_WIFI_STATE)
}
if (permissionRequest.isNotEmpty()){
permissionLauncher.launch(permissionRequest.toTypedArray())
}
}
}

View file

@ -0,0 +1,453 @@
package com.gaurav.avnc.ui.ldc
import android.Manifest
import android.app.Activity
import android.app.AlertDialog
import android.content.BroadcastReceiver
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.graphics.Color
import android.graphics.Typeface
import android.location.LocationManager
import android.net.wifi.WifiManager
import android.os.Bundle
import android.os.PowerManager
import android.provider.Settings
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TableLayout
import android.widget.TableRow
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import com.gaurav.avnc.R
import com.gaurav.avnc.ui.ldc.lib.ApplicationInfo
import com.gaurav.avnc.ui.ldc.lib.WifiFrequencies
import kotlin.math.max
import kotlin.math.min
class KioskDiagnosticActivity : AppCompatActivity(), View.OnClickListener {
// * Permissions
private lateinit var permissionLauncher: ActivityResultLauncher<Array<String>>
private var isInternetPermissionGranted = false
private var isNetworkStatePermissionGranted = false
private var isWifiStatePermissionGranted = false
private var isWifiStateChangePermissionGranted = false
private var isAccessFineLocationGranted = false
private var isAccessCoarseLocationGranted = false
// * View Elements
private lateinit var btnPing: Button
private lateinit var btnReboot: Button
private lateinit var btnRefresh: Button
private lateinit var btnGoBack: Button
private lateinit var tableLayoutWifiRows: TableLayout
private lateinit var progressBarWifiStrengthPercent: ProgressBar
private lateinit var textViewWifiStrengthPercent: TextView
// * AppInfo
private lateinit var appInfo: ApplicationInfo
// * WifiManager
private var wifiManagerListening = false
private lateinit var mWifiManager: WifiManager
private lateinit var mWifiScanReceiver: BroadcastReceiver
/**
* On Create
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_kiosk_diagnostic)
// * Setup AppInfo
appInfo = ApplicationInfo(PreferenceManager.getDefaultSharedPreferences(applicationContext))
// * Request Permissions
permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
isInternetPermissionGranted = permissions[Manifest.permission.INTERNET] ?: isInternetPermissionGranted
isNetworkStatePermissionGranted = permissions[Manifest.permission.ACCESS_NETWORK_STATE] ?: isNetworkStatePermissionGranted
isWifiStatePermissionGranted = permissions[Manifest.permission.ACCESS_WIFI_STATE] ?: isWifiStatePermissionGranted
isWifiStateChangePermissionGranted = permissions[Manifest.permission.CHANGE_WIFI_STATE] ?: isWifiStateChangePermissionGranted
isAccessFineLocationGranted = permissions[Manifest.permission.ACCESS_FINE_LOCATION] ?: isAccessFineLocationGranted
isAccessCoarseLocationGranted = permissions[Manifest.permission.ACCESS_COARSE_LOCATION] ?: isAccessCoarseLocationGranted
}
btnPing = findViewById<Button>(R.id.btnPing)
btnReboot = findViewById<Button>(R.id.btnReboot)
btnRefresh = findViewById<Button>(R.id.btnRefresh)
btnGoBack = findViewById<Button>(R.id.btnGoBack)
tableLayoutWifiRows = findViewById<TableLayout>(R.id.tableLayoutWifiRows)
progressBarWifiStrengthPercent = findViewById<ProgressBar>(R.id.progressBarWifiStrengthPercent)
textViewWifiStrengthPercent = findViewById<TextView>(R.id.textViewWifiStrengthPercent)
// * EVENTS
btnPing.setOnClickListener(this)
btnReboot.setOnClickListener(this)
btnRefresh.setOnClickListener(this)
btnGoBack.setOnClickListener(this)
// * Wont be able to => Hide
btnReboot.visibility = View.INVISIBLE
resetWifiTableView()
requestPermission()
}
/**
* Event on Activity Resume
*/
override fun onResume() {
super.onResume()
// * AppInfo Refresh
appInfo.refreshConfigProperties()
appInfo.retrieveDeviceNetworkInfo(applicationContext)
}
/**
* Event On Request permission result
*/
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
permissions.forEachIndexed { index, permission ->
when(permission) {
Manifest.permission.INTERNET -> isInternetPermissionGranted = grantResults[index] == PackageManager.PERMISSION_GRANTED
Manifest.permission.ACCESS_NETWORK_STATE -> isNetworkStatePermissionGranted = grantResults[index] == PackageManager.PERMISSION_GRANTED
Manifest.permission.ACCESS_WIFI_STATE -> isWifiStatePermissionGranted = grantResults[index] == PackageManager.PERMISSION_GRANTED
Manifest.permission.CHANGE_WIFI_STATE -> isWifiStateChangePermissionGranted = grantResults[index] == PackageManager.PERMISSION_GRANTED
Manifest.permission.ACCESS_FINE_LOCATION -> isAccessFineLocationGranted = grantResults[index] == PackageManager.PERMISSION_GRANTED
Manifest.permission.ACCESS_COARSE_LOCATION -> isAccessCoarseLocationGranted = grantResults[index] == PackageManager.PERMISSION_GRANTED
}
}
}
// * Action UI events
override fun onClick(v: View) {
when (v.id) {
R.id.btnPing -> {
startActivity(Intent(this, KioskPingActivity::class.java))
return
}
R.id.btnReboot -> {
startReboot()
return
}
R.id.btnRefresh -> {
requestPermission()
mWifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager
if(mWifiManager.wifiState === WifiManager.WIFI_STATE_DISABLED) {
resetWifiTableView()
toastMessage(this, "Wifi désactivé")
return
}
if (!checkPermissionsAreGranted()) {
toastMessage(this, "Permissions manquantes")
return
}
if (!canGetLocation()) {
showSettingsAlert()
return
}
initWifiScanner()
if(wifiManagerListening) {
toastMessage(this, "Rafraichissement des réseaux WiFi")
mWifiManager.startScan()
}
return
}
R.id.btnGoBack -> {
finish()
return
}
}
}
/**
* Initialize Wifi Scanner
*/
private fun initWifiScanner() {
val activity = this;
if(!checkPermissionsAreGranted()) {
toastMessage(activity, "Permissions manquantes pour scanner les réseaux WiFI")
return
}
if (wifiManagerListening) {
return
}
// * WIFI scan init
mWifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager
mWifiScanReceiver = object : BroadcastReceiver () {
override fun onReceive(c:Context, intent: Intent) {
// * Scan changed
resetWifiTableView()
val actualNetworkName =
if(mWifiManager.connectionInfo.ssid.length === 0)
mWifiManager.connectionInfo.bssid.replace('"', ' ').trim()
else mWifiManager.connectionInfo.ssid.replace('"', ' ').trim()
val actualNetworkFreq = getWifiType(mWifiManager.connectionInfo.frequency)
Log.d("ACTUAL NETWORK", actualNetworkName)
mWifiManager.scanResults.forEach{
/** From -100 to 0
* https://stackoverflow.com/questions/15797920/how-to-convert-wifi-signal-strength-from-quality-percent-to-rssi-dbm/15798024#15798024
*/
var precisePercent = if (it.level <= -100) 0 else if(it.level >= -50) 100 else 2 * (it.level + 100);
val row = LayoutInflater.from(this@KioskDiagnosticActivity)
.inflate(R.layout.activity_kiosk_diagnostic_wifi_row, null) as TableRow
val networkName = if(it.SSID.length === 0)
it.BSSID.replace('"', ' ').trim()
else it.SSID.replace('"', ' ').trim()
Log.d("NETWORK", networkName)
(row.findViewById<View>(R.id.tableCellSSID) as TextView).text = networkName.substring(0, min(networkName.length, 18))
(row.findViewById<View>(R.id.tableCellSignal) as TextView).text = it.level.toString()
(row.findViewById<View>(R.id.tableCellSignalPercent) as TextView).text = "$precisePercent %"
(row.findViewById<View>(R.id.tableCellSignalFreq) as TextView).text = getWifiType(it.frequency)
if (actualNetworkName == networkName && actualNetworkFreq == getWifiType(it.frequency)) {
val ssidTextView = (row.findViewById<View>(R.id.tableCellSSID) as TextView)
ssidTextView.setTypeface(ssidTextView.typeface, Typeface.BOLD_ITALIC)
ssidTextView.setTextColor(Color.BLACK)
}
tableLayoutWifiRows.addView(row)
}
// * Update Actual Wifi strength
val wifiInfo = mWifiManager.connectionInfo
val precisePercent = if (wifiInfo.rssi <= -100) 0 else if(wifiInfo.rssi >= -50) 100 else 2 * (wifiInfo.rssi + 100)
progressBarWifiStrengthPercent.max = 100
progressBarWifiStrengthPercent.min = 0
progressBarWifiStrengthPercent.progress = precisePercent
textViewWifiStrengthPercent.text = "$precisePercent %"
}
}
registerReceiver(mWifiScanReceiver, IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION))
registerReceiver(mWifiScanReceiver, IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION))
registerReceiver(mWifiScanReceiver, IntentFilter(WifiManager.EXTRA_RESULTS_UPDATED))
/**
* The WifiManager.startScan() usage is limited to: - Each foreground app is restricted to 4 scans every 2 minutes
* All background apps combined are restricted to one scan every 30 minutes.
* https://stackoverflow.com/questions/49178307/startscan-in-wifimanager-deprecated-in-android-p
*/
mWifiManager.startScan()
wifiManagerListening = true
}
/**
* Reset Wifi table View
*/
private fun resetWifiTableView() {
val childCount = tableLayoutWifiRows.childCount
if(childCount > 0) {
tableLayoutWifiRows.removeViews(0, max(0, childCount))
}
// * Append Table head
val row: TableRow = if (wifiManagerListening) {
LayoutInflater.from(this@KioskDiagnosticActivity)
.inflate(R.layout.activity_kiosk_diagnostic_wifi_header, null) as TableRow
} else {
LayoutInflater.from(this@KioskDiagnosticActivity)
.inflate(R.layout.activity_kiosk_diagnostic_wifi_press_refresh_row, null) as TableRow
}
tableLayoutWifiRows.addView(row)
}
/**
* Try to reboot device
*/
private fun startReboot()
{
try {
val pm: PowerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
pm.reboot(null)
} catch(e: UnsupportedOperationException) {
toastMessage(this ,"Erreur (${e.message}) : ${e.cause?.message}")
} catch (e: SecurityException) {
toastMessage(this ,"Erreur (${e.message}) : ${e.cause?.message}")
} finally {
toastMessage(this ,"Droits insufisants")
}
}
/**
* Show settings for location
* To allow user ot enable this
*/
private fun showSettingsAlert() {
val alertDialog: AlertDialog.Builder = AlertDialog.Builder(this)
// Setting Dialog Title
alertDialog.setTitle("Attention !")
// Setting Dialog Message
alertDialog.setMessage("Pour scanner les réseaux wifi, la localisation doit être activée.")
// On pressing Settings button
alertDialog.setPositiveButton(
"Accéder à la configuration",
DialogInterface.OnClickListener { _, _ ->
startActivity(Intent(
Settings.ACTION_LOCATION_SOURCE_SETTINGS
))
})
alertDialog.show()
}
/**
* Check if we can get location
* It has to be enabled to access wifi networks
*/
private fun canGetLocation(): Boolean {
var gpsEnabled = false
var networkEnabled = false
var passiveLocationEnabled = false
val lm: LocationManager = getSystemService(LOCATION_SERVICE) as LocationManager
// exceptions will be thrown if provider is not permitted.
try {
gpsEnabled = lm.isProviderEnabled(LocationManager.GPS_PROVIDER)
} catch (ex: Exception) {
toastMessage(this, "Erreur " + ex.message)
}
try {
passiveLocationEnabled = lm.isProviderEnabled(LocationManager.PASSIVE_PROVIDER)
} catch (ex: Exception) {
toastMessage(this, "Erreur " + ex.message)
}
try {
networkEnabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
} catch (ex: Exception) {
toastMessage(this, "Erreur " + ex.message)
}
return gpsEnabled || networkEnabled || passiveLocationEnabled
}
/**
* Display toast message
*/
private fun toastMessage(activity: Activity, message: String) {
activity.runOnUiThread {
Toast.makeText(
activity.applicationContext,
message,
Toast.LENGTH_LONG
).show()
}
}
/**
* Check if all permissions are granted
*/
private fun checkPermissionsAreGranted(): Boolean
{
isInternetPermissionGranted = ContextCompat.checkSelfPermission(
this,
Manifest.permission.INTERNET
) == PackageManager.PERMISSION_GRANTED
isNetworkStatePermissionGranted = ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_NETWORK_STATE
) == PackageManager.PERMISSION_GRANTED
isWifiStatePermissionGranted = ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_WIFI_STATE
) == PackageManager.PERMISSION_GRANTED
isWifiStateChangePermissionGranted = ContextCompat.checkSelfPermission(
this,
Manifest.permission.CHANGE_WIFI_STATE
) == PackageManager.PERMISSION_GRANTED
isAccessFineLocationGranted = ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
isAccessCoarseLocationGranted = ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED
return isInternetPermissionGranted && isNetworkStatePermissionGranted &&
isWifiStatePermissionGranted && isWifiStateChangePermissionGranted &&
isAccessFineLocationGranted && isAccessCoarseLocationGranted
}
/**
* Request application permissions
*/
private fun requestPermission() {
if (checkPermissionsAreGranted()) {
return
}
val permissionRequest: MutableList<String> = ArrayList()
if (!isInternetPermissionGranted) {
permissionRequest.add(Manifest.permission.INTERNET)
}
if (!isNetworkStatePermissionGranted) {
permissionRequest.add(Manifest.permission.ACCESS_NETWORK_STATE)
}
if (!isWifiStatePermissionGranted) {
permissionRequest.add(Manifest.permission.ACCESS_WIFI_STATE)
}
if (!isWifiStateChangePermissionGranted) {
permissionRequest.add(Manifest.permission.CHANGE_WIFI_STATE)
}
if (!isAccessFineLocationGranted) {
permissionRequest.add(Manifest.permission.ACCESS_FINE_LOCATION)
}
if (!isAccessCoarseLocationGranted) {
permissionRequest.add(Manifest.permission.ACCESS_COARSE_LOCATION)
}
ActivityCompat.requestPermissions(this@KioskDiagnosticActivity, permissionRequest.toTypedArray(), 100)
}
/**
* Get the wifi network type based on frequency
*/
private fun getWifiType(frequency: Int): String {
if (WifiFrequencies.AH_LOW.freq.rangeTo(WifiFrequencies.AH_HIGH.freq).contains(frequency)) {
return "860/900 MHz"
}
if (WifiFrequencies.BGNAX_LOW.freq.rangeTo(WifiFrequencies.BGNAX_HIGH.freq).contains(frequency)) {
return "2.4Ghz"
}
if (WifiFrequencies.Y_LOW.freq.rangeTo(WifiFrequencies.Y_HIGH.freq).contains(frequency)) {
return "3.65 GHz (US)"
}
if (WifiFrequencies.J_LOW.freq.rangeTo(WifiFrequencies.J_HIGH.freq).contains(frequency)) {
return "4.9–5.0 GHz (J)"
}
if (WifiFrequencies.AHNACAX_LOW.freq.rangeTo(WifiFrequencies.AHNACAX_HIGH.freq).contains(frequency)) {
return "5Ghz"
}
return "$frequency N/a"
}
}

View file

@ -0,0 +1,146 @@
package com.gaurav.avnc.ui.ldc
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import com.gaurav.avnc.R
import com.gaurav.avnc.model.ServerProfile
import com.gaurav.avnc.ui.ldc.lib.ApplicationInfo
import com.gaurav.avnc.ui.ldc.lib.exceptions.InvalidUriException
import com.gaurav.avnc.viewmodel.HomeViewModel
import java.net.InetAddress
import java.net.UnknownHostException
class KioskParameterActivity : AppCompatActivity(), View.OnClickListener {
// * View Elements
private lateinit var btnValidateConfig: Button
private lateinit var btnGoBack: Button
private lateinit var btnDiagnostic: Button
private lateinit var editTextServerUrl: EditText
private lateinit var editTextIp: EditText
private lateinit var editTextPort: EditText
// * AppInfo
private lateinit var appInfo: ApplicationInfo
/**
* On Create
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_kiosk_parameter)
btnValidateConfig = findViewById<Button>(R.id.btnValidateConfig)
btnGoBack = findViewById<Button>(R.id.btnGoBack)
btnDiagnostic = findViewById<Button>(R.id.btnDiagnostic)
editTextServerUrl = findViewById<EditText>(R.id.editTextServerUrl)
editTextIp = findViewById<EditText>(R.id.editTextIp)
editTextPort = findViewById<EditText>(R.id.editTextPort)
// * EVENTS
btnDiagnostic.setOnClickListener(this)
btnValidateConfig.setOnClickListener(this)
btnGoBack.setOnClickListener(this)
// * Setup AppInfo
appInfo = ApplicationInfo(getDefaultSharedPreferences(applicationContext))
// * Assign inputs
editTextServerUrl.setText(appInfo.configServerUri.toString())
editTextIp.setText(appInfo.vncArgIp.hostAddress)
editTextPort.setText(appInfo.vncArgPort.toString())
}
/**
* Event on Activity Resume
*/
override fun onResume() {
super.onResume()
// * AppInfo Refresh
appInfo.refreshConfigProperties()
appInfo.retrieveDeviceNetworkInfo(applicationContext)
}
/**
* On Back pressed
*/
override fun onBackPressed() {
navigateUpTo(Intent(baseContext, KioskActivity::class.java))
finish()
}
// * Action UI events
override fun onClick(v: View) {
when (v.id) {
R.id.btnValidateConfig -> {
validateAndSaveConfig()
return
}
R.id.btnGoBack -> {
finish()
navigateUpTo(Intent(baseContext, KioskActivity::class.java))
return
}
R.id.btnDiagnostic -> {
startActivity(Intent(this, KioskDiagnosticActivity::class.java))
return
}
}
}
private fun validateAndSaveConfig() {
try {
appInfo.setConfigUri(Uri.parse(editTextServerUrl.text.toString()))
} catch(e: InvalidUriException) {
toastMessage(this, "L'url du fichier est invalide")
}
try {
appInfo.setVncIpPort(
InetAddress.getByName(editTextIp.text.toString()),
editTextPort.text.toString().toInt()
)
} catch(e: NumberFormatException) {
toastMessage(this, "Le port n'est pas valide")
} catch(e: UnknownHostException) {
toastMessage(this, "L'adresse ip n'est pas valide")
}
Thread {
val viewModel by viewModels<HomeViewModel>()
var profile = appInfo.loadProfile(viewModel)
if (profile == null) {
profile = ServerProfile()
profile.host = appInfo.vncArgIp.hostAddress
profile.port = appInfo.vncArgPort
viewModel.insertProfile(profile)
} else {
profile.host = appInfo.vncArgIp.hostAddress
profile.port = appInfo.vncArgPort
viewModel.updateProfile(profile)
}
toastMessage(this, "Enregistré")
}.start()
}
/**
* Display toast message
*/
private fun toastMessage(activity: Activity, message: String) {
activity.runOnUiThread(Runnable {
Toast.makeText(
activity.applicationContext,
message,
Toast.LENGTH_LONG
).show()
})
}
}

View file

@ -0,0 +1,111 @@
package com.gaurav.avnc.ui.ldc
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Button
import android.widget.EditText
import android.widget.TextView.OnEditorActionListener
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager.getDefaultSharedPreferences
import com.gaurav.avnc.R
import com.gaurav.avnc.ui.ldc.lib.ApplicationInfo
class KioskParameterPasswordActivity : AppCompatActivity(), View.OnClickListener {
// * View Elements
private lateinit var editTextPassword: EditText
private lateinit var btnSubmitPassword: Button
private lateinit var btnGoBack: Button
// * AppInfo
lateinit var appInfo: ApplicationInfo
private set
/**
* On Create
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_kiosk_parameter_password)
editTextPassword = findViewById<EditText>(R.id.editTextPassword)
btnSubmitPassword = findViewById<Button>(R.id.btnSubmitPassword)
btnGoBack = findViewById<Button>(R.id.btnGoBack)
// * EVENTS
editTextPassword.setOnClickListener(this)
btnSubmitPassword.setOnClickListener(this)
btnGoBack.setOnClickListener(this)
editTextPassword.setOnEditorActionListener(OnEditorActionListener { _, actionId, _ ->
var handled = false
if (actionId == EditorInfo.IME_ACTION_DONE) {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(editTextPassword.windowToken, 0)
handled = true
}
handled
})
// * Setup AppInfo
appInfo = ApplicationInfo(getDefaultSharedPreferences(applicationContext))
}
/**
* Event on Activity Resume
*/
override fun onResume() {
super.onResume()
// * AppInfo Refresh
appInfo.refreshConfigProperties()
appInfo.retrieveDeviceNetworkInfo(applicationContext)
}
// * Action UI events
override fun onClick(v: View) {
when (v.id) {
R.id.btnSubmitPassword -> {
validatePassword()
return
}
R.id.btnGoBack -> {
finish()
return
}
}
}
/**
* Validate password and go to next activity
* Simple validation pour next intent, pas besoin de haute sécurité
*/
private fun validatePassword() {
editTextPassword.clearFocus()
if(editTextPassword.text.toString() != this.appInfo.configPassword) {
toastMessage(this,"Mot de passe invalide")
runOnUiThread { editTextPassword.setTextColor(Color.RED) }
return
}
runOnUiThread {editTextPassword.setTextColor(Color.BLACK) }
startActivity(Intent(this, KioskParameterActivity::class.java))
}
/**
* Display toast message
*/
private fun toastMessage(activity: Activity, message: String) {
activity.runOnUiThread(Runnable {
Toast.makeText(
activity.applicationContext,
message,
Toast.LENGTH_LONG
).show()
})
}
}

View file

@ -0,0 +1,259 @@
package com.gaurav.avnc.ui.ldc
import android.app.Activity
import android.graphics.Color
import android.os.Bundle
import android.os.StrictMode
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.toColorInt
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import com.gaurav.avnc.R
import com.gaurav.avnc.ui.ldc.lib.ApplicationInfo
import com.gaurav.avnc.ui.ldc.lib.exceptions.InvalidIpException
import com.gaurav.avnc.ui.ldc.lib.exceptions.PingFailedException
import com.google.common.net.InetAddresses
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.net.Inet4Address
import java.net.Inet6Address
import java.net.InetAddress
import java.net.UnknownHostException
import kotlin.math.roundToInt
class KioskPingActivity : AppCompatActivity(), View.OnClickListener {
// * View Elements
private lateinit var editTextPingUrl: EditText
private lateinit var btnPing: Button
private lateinit var btnGoBack: Button
private lateinit var textResult: TextView
// * AppInfo
private lateinit var appInfo: ApplicationInfo
/**
* On Create
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
setContentView(R.layout.activity_kiosk_ping)
editTextPingUrl = findViewById<EditText>(R.id.editTextPingUrl)
btnPing = findViewById<Button>(R.id.btnPing)
btnGoBack = findViewById<Button>(R.id.btnGoBack)
textResult = findViewById<TextView>(R.id.textResult)
// * EVENTS
btnPing.setOnClickListener(this)
btnGoBack.setOnClickListener(this)
// * Setup AppInfo
appInfo = ApplicationInfo(PreferenceManager.getDefaultSharedPreferences(applicationContext))
// * Init text result
textResult.visibility = View.INVISIBLE
}
/**
* Event on Activity Resume
*/
override fun onResume() {
super.onResume()
// * AppInfo Refresh
appInfo.refreshConfigProperties()
appInfo.retrieveDeviceNetworkInfo(applicationContext)
editTextPingUrl.setText(appInfo.gateway.hostAddress)
}
// * Action UI events
override fun onClick(v: View) {
when (v.id) {
R.id.btnPing -> {
ping()
return
}
R.id.btnGoBack -> {
finish()
return
}
}
}
/**
* Ping action
*/
private fun ping() {
// * Prepare UI
editTextPingUrl.clearFocus()
textResult.text = "En cours.."
textResult.setTextColor(Color.BLACK)
textResult.visibility = View.VISIBLE
// * Disable ping BTN
btnPing.isEnabled = false
btnPing.isClickable = false
btnPing.setBackgroundColor(Color.GRAY)
// * Disable textBox
editTextPingUrl.isEnabled = false
editTextPingUrl.isClickable = false
val elementToPing = editTextPingUrl.text.toString()
lifecycleScope.executeAsyncTask(onPreExecute = {}, doInBackground = {
Thread {
try {
lateinit var inetToPing: InetAddress
// ? Is IP
if (isValidIpAddress(elementToPing)) {
// Resolve
inetToPing = InetAddress.getByName(elementToPing)
} else {
// * Is FQDN
var host = elementToPing.toHttpUrlOrNull()?.host
// Resolve
inetToPing = InetAddress.getByName(if(host !== null) host else elementToPing)
}
// * Ping ping
val avg = pingHost(inetToPing)
toastMessage(this, "FQDN AVG $avg ms")
runOnUiThread {
textResult.text = "Accessible ${inetToPing.hostAddress}"
textResult.setTextColor("#009933".toColorInt())
}
return@Thread
} catch(e: PingFailedException) {
runOnUiThread {
textResult.text = "Echec du ping"
textResult.setTextColor(Color.RED)
}
} catch(e: InvalidIpException) {
runOnUiThread {
textResult.text = "Echec du ping ip invalide"
textResult.setTextColor(Color.RED)
}
} catch(e: SecurityException) {
toastMessage(this, "Erreur : ${e.message} - ${e.cause?.message}")
runOnUiThread {
textResult.text = "Echec du ping erreur de sécurité"
textResult.setTextColor(Color.RED)
}
} catch(e: UnknownHostException) {
toastMessage(this, "Echec du ping hote inconnu")
runOnUiThread {
textResult.text = "Echec du ping !!"
textResult.setTextColor(Color.RED)
}
} catch(e: IOException) {
toastMessage(this, "Echec du ping erreur de connection")
runOnUiThread {
textResult.text = "Echec du ping !!"
textResult.setTextColor(Color.RED)
}
}
}.run()
}, onPostExecute = {
// * Enable ping BTN
btnPing.isEnabled = true
btnPing.isClickable = true
btnPing.setBackgroundColor("#D4021D".toColorInt())
// * Enable textBox
editTextPingUrl.isEnabled = true
editTextPingUrl.isClickable = true
})
}
/**
* Ping host and return average time in ms
* @throws PingFailedException In case of failure
* @throws InvalidIpException If inetAddress is not a valid IPv4 or IPv6
*/
private fun pingHost(inetToPing: InetAddress): Int {
// ? Is Valid IP
if (!isValidIpAddress(inetToPing.hostAddress)) {
throw InvalidIpException()
}
inetToPing.isReachable(600)
var avg = -1
lateinit var process: Process
if (inetToPing is Inet6Address) {
process = Runtime.getRuntime().exec("/system/bin/ping6 -c 1 ${inetToPing.hostAddress}")
} else if (inetToPing is Inet4Address) {
process = Runtime.getRuntime().exec("/system/bin/ping -c 1 ${inetToPing.hostAddress}")
} else {
throw InvalidIpException()
}
BufferedReader(InputStreamReader(process.inputStream)).forEachLine {
// it: "PING 194.194.194.194 (194.194.194.194) 56(84) bytes of data."
// it: "rtt min/avg/max/mdev = 326.137/326.137/326.137/0.000 ms"
if (it.contains("rtt ")) {
avg = Regex("\\d+\\.\\d+").findAll(it).toList()[1].value.toFloat().roundToInt()
}
}
if (avg === -1) {
toastMessage(this, "IP erreur de connection")
throw PingFailedException()
}
return avg
}
/**
* Test if ip Address is valid
*/
private fun isValidIpAddress(str: String): Boolean {
if (str.isEmpty())
{
return false
}
// Regex for IPV4 and IPV6
return InetAddresses.isInetAddress(str)
}
/**
* Display toast message
*/
private fun toastMessage(activity: Activity, message: String) {
activity.runOnUiThread(Runnable {
Toast.makeText(
activity.applicationContext,
message,
Toast.LENGTH_LONG
).show()
})
}
fun <R> CoroutineScope.executeAsyncTask(
onPreExecute: () -> Unit,
doInBackground: () -> R,
onPostExecute: (R) -> Unit
) = launch {
onPreExecute()
val result = withContext(Dispatchers.IO) { // runs in background thread without blocking the Main Thread
doInBackground()
}
onPostExecute(result)
}
}

View file

@ -0,0 +1,6 @@
package com.gaurav.avnc.ui.ldc.events
/**
* The network connection state has changed
*/
class AppSwitchedToForegroundEvent()

View file

@ -0,0 +1,6 @@
package com.gaurav.avnc.ui.ldc.events
/**
* The network connection state has changed
*/
class NetworkStateChangedEvent()

View file

@ -0,0 +1,503 @@
package com.gaurav.avnc.ui.ldc.fragments
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import androidx.core.graphics.toColorInt
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.JsonMappingException
import com.fasterxml.jackson.databind.ObjectMapper
import com.gaurav.avnc.R
import com.gaurav.avnc.model.ServerProfile
import com.gaurav.avnc.ui.ldc.KioskActivity
import com.gaurav.avnc.ui.ldc.KioskParameterPasswordActivity
import com.gaurav.avnc.ui.ldc.events.AppSwitchedToForegroundEvent
import com.gaurav.avnc.ui.ldc.events.NetworkStateChangedEvent
import com.gaurav.avnc.ui.ldc.lib.exceptions.PasswordValidationException
import com.gaurav.avnc.ui.vnc.startVncActivity
import com.gaurav.avnc.viewmodel.HomeViewModel
import com.google.common.net.InetAddresses
import com.networknt.schema.JsonSchemaFactory
import com.networknt.schema.SchemaValidatorsConfig
import com.networknt.schema.SpecVersion
import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.apache.commons.csv.CSVFormat
import org.apache.commons.csv.CSVParser
import org.apache.commons.csv.CSVRecord
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStream
import java.io.Reader
import java.net.InetAddress
import java.util.regex.Matcher
import java.util.regex.Pattern
/**
* HOME TAB
*/
class KioskHomeFragment : Fragment(), View.OnClickListener {
// * View Elements
private lateinit var txtMacAddress: TextView
private lateinit var txtIPAddress: TextView
private lateinit var txtVNCArgs: TextView
private lateinit var btnLoadConfig: Button
private lateinit var btnVNC: Button
private lateinit var btnParameters: Button
private lateinit var progressBar: ProgressBar
// * EVENTS
/**
* On Start
*/
override fun onStart() {
super.onStart()
}
/**
* On Stop
*/
override fun onStop() {
super.onStop()
}
/**
* On Create
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
/**
* On Destroy
*/
override fun onDestroy() {
super.onDestroy()
// * Register event bus
EventBus.getDefault().unregister(this)
}
/**
* On Create view
*/
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view:View = inflater.inflate(R.layout.fragment_kiosk_home, container, false)
txtIPAddress = view.findViewById<TextView>(R.id.txtIPAddress)
txtMacAddress = view.findViewById<TextView>(R.id.txtMacAddress)
txtVNCArgs = view.findViewById<TextView>(R.id.txtVNCArgs)
btnLoadConfig = view.findViewById<Button>(R.id.btnLoadConfig)
btnVNC = view.findViewById<Button>(R.id.btnVNC)
btnParameters = view.findViewById<Button>(R.id.btnParameters)
progressBar = view.findViewById<ProgressBar>(R.id.progressBar)
// * EVENTS
btnLoadConfig.setOnClickListener(this)
btnVNC.setOnClickListener(this)
btnParameters.setOnClickListener(this)
// * Register event bus
EventBus.getDefault().register(this)
return view;
}
/**
* Event on Activity Resume
*/
override fun onResume() {
super.onResume()
val activity = (this.activity as KioskActivity)
// * AppInfo Refresh
activity.appInfo.refreshConfigProperties()
activity.appInfo.retrieveDeviceNetworkInfo(activity.applicationContext)
showNetworkInfo(activity.appInfo.networkConnected)
showVNCArgs()
}
/**
* On App Background
*/
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: AppSwitchedToForegroundEvent?) {
val activity = (activity as KioskActivity)
showNetworkInfo(activity.appInfo.networkConnected)
}
/**
* On network message received
*/
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: NetworkStateChangedEvent?) {
val activity = (activity as KioskActivity)
showNetworkInfo(activity.appInfo.networkConnected)
}
// * Action UI events
override fun onClick(v: View) {
when (v.id) {
R.id.btnVNC -> btnStartVncSessionClick(v, this)
R.id.btnLoadConfig -> btnLoadConfigClick(v, this)
R.id.btnParameters -> {
startActivity(Intent(this.context, KioskParameterPasswordActivity::class.java))
}
}
}
/**
* Action on click button "Load config"
*/
private fun btnLoadConfigClick(view:View, fragment: KioskHomeFragment) {
val activity = (fragment.activity as KioskActivity)
manageButtons(false)
// Check we are connected to a network
if (!activity.appInfo.networkConnected) {
toastMessage(activity, "Vous devez être connecté à un réseau")
}
manageButtons(true)
// Download Config File
loadDistantConfiguration(fragment)
// Download Password config
fetchConfigPassword(fragment)
}
/**
* Action on click button "Load config"
*/
private fun btnStartVncSessionClick(view:View, fragment: KioskHomeFragment) {
val activity = (fragment.activity as KioskActivity)
val viewModel by viewModels<HomeViewModel>()
Thread {
if (!activity.appInfo.networkConnected) {
toastMessage(activity, "Vous devez être connecté à un réseau")
return@Thread
}
val profile = activity.appInfo.loadProfile(viewModel)
if (profile == null) {
toastMessage(activity, "La configuration est manquante")
} else {
startVncActivity(activity, profile)
}
}.start()
}
// * PRIVATE METHODS
/**
* Load config csv from server
*/
private fun loadDistantConfiguration(fragment: KioskHomeFragment) {
val activity = (fragment.activity as KioskActivity)
val viewModel by viewModels<HomeViewModel>()
val client = OkHttpClient()
val request = Request.Builder()
.url(activity.appInfo.configServerUri.toString())
.build()
val callback = object : Callback {
override fun onFailure(call: Call, e: IOException) {
toastMessage(activity, "Erreur : ${e.message} - ${e.cause?.message}")
manageButtons(true)
return
//e.printStackTrace()
}
@SuppressLint("SetTextI18n")
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) {
toastMessage(activity, "Erreur (${response.code}) : ${response.message}")
manageButtons(true)
return
}
val res: Reader = response.body!!.charStream();
val bufferedReader: BufferedReader = BufferedReader(res);
val parser = CSVParser.parse(bufferedReader, CSVFormat.DEFAULT.withDelimiter(';'));
for (csvRecord: CSVRecord in parser) {
val identifier = csvRecord.get(0);
// Si le premier caractère est un dieze la ligne est commentée donc on passe
if (identifier[0] == '#' || identifier.isEmpty()) {
continue;
}
// On test si l´Identifiant TID correspond à celle du Device
if (identifier.length == 11 && identifier.equals(activity.appInfo.tidId)) {
val ip = csvRecord.get(3);
val port = csvRecord.get(4);
if (ip!!.isEmpty() || !isValidIpAddress(ip)) {
toastMessage(activity, "Adresse IP invalide")
manageButtons(true)
return
}
if (port!!.isEmpty() || !isValidPort(port)) {
toastMessage(activity, "Port invalide")
manageButtons(true)
return
}
var profile = activity.appInfo.loadProfile(viewModel)
if (profile == null) {
profile = ServerProfile()
profile.host = InetAddress.getByName(ip).hostAddress
profile.port = port.toInt()
viewModel.insertProfile(profile)
} else {
profile.host = InetAddress.getByName(ip).hostAddress
profile.port = port.toInt()
viewModel.updateProfile(profile)
}
// * AppInfo Refresh
activity.appInfo.setVncIpPort(InetAddress.getByName(ip), port.toInt())
activity.runOnUiThread {
fragment.txtVNCArgs?.text = "Arguments VNC : ${profile.host}:${profile.port}"
}
toastMessage(activity, "Configuration chargée")
manageButtons(true)
return
}
}
toastMessage(activity, "Configuration non trouvée")
manageButtons(true)
return
}
}
}
client.newCall(request).enqueue(callback)
}
/**
* Try to fetch config password from server
*/
private fun fetchConfigPassword(fragment: KioskHomeFragment) {
val activity = (fragment.activity as KioskActivity)
val client = OkHttpClient()
val request = Request.Builder()
.url(activity.appInfo.appConfigUri.toString())
.build()
val callback = object : Callback {
override fun onFailure(call: Call, e: IOException) {
toastMessage(activity, "Erreur : ${e.message} - ${e.cause?.message}")
return
}
@SuppressLint("SetTextI18n")
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) {
toastMessage(activity ,"Erreur (${response.code}) : ${response.message}")
return
}
val bodyResponse = response.body?.string()
if (bodyResponse == null) {
toastMessage(activity, "Erreur réponse vide du serveur")
return
}
if(!isJSONValid(activity, bodyResponse)) {
toastMessage(activity, "Erreur réponse invalide du serveur")
return
} else {
try {
saveJSONPassword(activity, bodyResponse)
} catch(e: PasswordValidationException) {
toastMessage(activity, "Erreur mot de passe sur le serveur est invalide")
}
}
return
}
}
}
client.newCall(request).enqueue(callback)
}
/**
* Validate Json parameter schema
*/
private fun isJSONValid(activity: KioskActivity, jsonString: String): Boolean {
// * Validate JSON
try {
JSONObject(jsonString)
} catch (ex: JSONException) {
try {
JSONArray(jsonString)
} catch (ex1: JSONException) {
return false
}
}
// * Validate JSON Schema
return try {
val inputStream: InputStream = resources.assets.open("security.schema.json");
val schemaString = inputStream.reader().readText()
val schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V4)
val schema = schemaFactory.getSchema(schemaString)
val config: SchemaValidatorsConfig = SchemaValidatorsConfig()
Log.d("test", config.toString())
val jsonNode = ObjectMapper().readTree(jsonString)
// * Validate json against schema
val errors = schema.validate(jsonNode)
if (errors.size > 0) {
Log.d("JSON", "Invalid JSON against schema")
return false
}
return true
} catch(e: IOException) {
false
} catch(e: JsonProcessingException) {
Log.d("JSON", e.message)
false
} catch(e: JsonMappingException) {
Log.d("JSON", e.message)
false
}
}
/**
* Save Json password
* @throws com.gaurav.avnc.ui.ldc.lib.exceptions.PasswordValidationException
*/
private fun saveJSONPassword(activity: KioskActivity, jsonString: String) {
val mapper = ObjectMapper()
val configNode = mapper.readValue<Map<String, String>>(jsonString, object : TypeReference<Map<String, String>>() {})
activity.appInfo.setPassword(configNode["password"]!!)
}
/**
* Display network info
*/
@SuppressLint("HardwareIds")
private fun showNetworkInfo(isConnected: Boolean) {
val activity = (activity as KioskActivity)
val ip = activity.appInfo.ip
val identifier = activity.appInfo.tidId
if (activity.appInfo.networkConnected) {
txtIPAddress?.text = "Adresse IP: ${ip?.hostAddress}"
txtMacAddress?.text = "Identifiant TID: $identifier"
} else {
txtIPAddress?.text = "Adresse IP: -"
txtMacAddress?.text = "Identifiant TID: $identifier"
}
}
/**
* Display VNCArgs
*/
private fun showVNCArgs() {
val activity = (activity as KioskActivity)
val ipVNC = activity.appInfo.vncArgIp.hostAddress
val portVNC = activity.appInfo.vncArgPort
//println("IPIP" + ipVNC)
txtVNCArgs?.text = "Arguments VNC : $ipVNC:$portVNC"
}
/**
* Manage interface buttons state
*/
private fun manageButtons(enable: Boolean) {
val activity = (activity as KioskActivity)
activity.runOnUiThread {
if (enable) {
btnLoadConfig?.isEnabled = true
btnLoadConfig?.isClickable = true
btnLoadConfig?.setBackgroundColor("#D4021D".toColorInt())
btnVNC?.isEnabled = true
btnVNC?.isClickable = true
btnVNC?.setBackgroundColor("#D4021D".toColorInt())
progressBar?.isVisible = false
} else {
btnLoadConfig?.isEnabled = false
btnLoadConfig?.isClickable = false
btnLoadConfig?.setBackgroundColor(Color.GRAY)
btnVNC?.isEnabled = false
btnVNC?.isClickable = false
btnVNC?.setBackgroundColor(Color.GRAY)
progressBar?.isVisible = true
}
}
}
/**
* Test if ip Address is valid
*/
private fun isValidIpAddress(str: String): Boolean {
if (str.isEmpty())
{
return false
}
// Regex for IPV4 and IPV6
return InetAddresses.isInetAddress(str)
}
/**
* Test if port is valid
*/
private fun isValidPort(str: String): Boolean {
if (str.isEmpty())
{
return false
}
try {
str.toInt()
} catch (e: NumberFormatException) {
return false
}
val regex: String =
"^((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4}))\$"
val p: Pattern = Pattern.compile(regex)
val m: Matcher = p.matcher(str)
return m.matches()
}
/**
* Display toast message
*/
private fun toastMessage(activity: Activity, message: String) {
activity.runOnUiThread(Runnable {
Toast.makeText(
activity.applicationContext,
message,
Toast.LENGTH_LONG
).show()
})
}
}

View file

@ -0,0 +1,144 @@
package com.gaurav.avnc.ui.ldc.fragments
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.children
import androidx.fragment.app.Fragment
import com.gaurav.avnc.R
import com.gaurav.avnc.ui.ldc.KioskActivity
import com.gaurav.avnc.ui.ldc.events.AppSwitchedToForegroundEvent
import com.gaurav.avnc.ui.ldc.events.NetworkStateChangedEvent
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
/**
* INFORMATION'S TAB
*/
class KioskInformationFragment : Fragment(), View.OnClickListener {
// * View Elements
private lateinit var infoLinearLayout: LinearLayout
/**
* On Start
*/
override fun onStart() {
super.onStart()
}
/**
* On Stop
*/
override fun onStop() {
super.onStop()
}
/**
* On Create
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// * Register event bus
EventBus.getDefault().register(this)
}
/**
* On Destroy
*/
override fun onDestroy() {
super.onDestroy()
// * Register event bus
EventBus.getDefault().unregister(this)
}
/**
* On Create view
*/
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view:View = inflater.inflate(R.layout.fragment_kiosk_information, container, false)
infoLinearLayout = view.findViewById<LinearLayout>(R.id.infoLinearLayout)
return view;
}
/**
* Event on Activity Resume
*/
override fun onResume() {
super.onResume()
val activity = (this.activity as KioskActivity)
// * AppInfo Refresh
activity.appInfo.refreshConfigProperties()
activity.appInfo.retrieveDeviceNetworkInfo(activity.applicationContext)
displayTextInfo()
}
// * Action UI events
override fun onClick(v: View) {
Log.d("click", "click")
}
/**
* On App Background
*/
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: AppSwitchedToForegroundEvent?) {
displayTextInfo()
}
/**
* On network message received
*/
@Subscribe(threadMode = ThreadMode.MAIN)
fun onMessageEvent(event: NetworkStateChangedEvent?) {
Log.d("network", "network state changed on kiosk fragment")
displayTextInfo()
}
/**
* Update Text Info
*/
private fun displayTextInfo() {
// * Load actual network configuration
val activity = (activity as KioskActivity)
val cells = infoLinearLayout.children as Sequence<TextView>
val ip = activity.appInfo.ip
val gateway = activity.appInfo.gateway
val info:Map<String, String> = mapOf(
"Identifiant TID" to activity.appInfo.tidId,
"Adresse IP" to if(ip !== null) ip.hostAddress else "-",
"Gateway" to if(gateway !== null) gateway.hostAddress else "-",
"Serveur de Config" to activity.appInfo.configServerUri.toString(),
"Système" to activity.appInfo.systemVERSION,
"Kiosk" to activity.appInfo.appVERSION,
"Dernière MàJ" to activity.appInfo.buildTimestamp.toLocaleString(),
)
info.onEachIndexed(fun (index, infoLine) {
(cells.elementAt(index ) as TextView).text = infoLine.key + "\t" + infoLine.value
})
}
private fun reloadNetWorkInfo (view: View, fragment: KioskInformationFragment) {
EventBus.getDefault().post(NetworkStateChangedEvent())
val activity = (fragment.activity as KioskActivity)
activity.appInfo.retrieveDeviceNetworkInfo(activity.applicationContext)
displayTextInfo()
EventBus.getDefault().post(NetworkStateChangedEvent())
}
}

View file

@ -0,0 +1,298 @@
package com.gaurav.avnc.ui.ldc.lib
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.net.ConnectivityManager
import android.net.Uri
import android.os.Build
import android.webkit.URLUtil
import com.gaurav.avnc.BuildConfig
import com.gaurav.avnc.model.ServerProfile
import com.gaurav.avnc.ui.ldc.lib.exceptions.InvalidUriException
import com.gaurav.avnc.ui.ldc.lib.exceptions.PasswordValidationException
import com.gaurav.avnc.viewmodel.HomeViewModel
import com.serialIntCaster.SerialCaster
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import java.net.InetAddress
import java.util.Date
import java.util.regex.Matcher
import java.util.regex.Pattern
import kotlin.reflect.KProperty1
/**
* This class stores application Info used for app execution
* and info display
*/
class ApplicationInfo(private var preferences: SharedPreferences) {
// * Static Elements (Readonly as val)
/** Android config filename */
private val appConfigFile = "security.json"
/** The unique TID identifier generated with SerialIntCaster */
val tidId: String = if(BuildConfig.DEBUG) "2T63SSH2D8F" else fetchIdentifier()
/** Android OS version */
val systemVERSION: String = "Android " + Build.VERSION.RELEASE
/** App version */
val appVERSION: String = BuildConfig.VERSION_NAME
/** App build timestamp generated using build.gradle */
val buildTimestamp: Date = BuildConfig.BUILD_TIME
/**
* Shared preferences items for dynamic update
*/
private val sharedPreferencesFields = mapOf<String,Set<String>>(
"Int" to setOf<String>("vncArgPort"),
"InetAddress" to setOf<String>("vncArgIp"),
"String" to setOf<String>("configPassword", "configServerFqdn", "configServerPath", "appConfigPath"),
"Uri" to setOf<String>("configServerUri", "appConfigUri")
)
// * Dynamic elements
/** Is the device actually connected to a network */
var networkConnected: Boolean = false
/** The device network ip that is used */
var ip: InetAddress? = null
private set
/** The device network gateway that is used */
var gateway: InetAddress = InetAddress.getByName("127.0.0.1")
private set
// * Dynamic elements stored prefs
/** The VNC ip fetched from server */
var vncArgIp: InetAddress = InetAddress.getByName("127.0.0.1")
private set
/** The VNC port fetched from server */
var vncArgPort: Int = 5900
private set
/** The default password for configuration */
var configPassword: String = "73ezheJ8"
private set
/** The server FQDN from where config should be fetched */
private var configServerFqdn: String = "http://localtid"
/** VNC config path */
private var configServerPath: String = if(BuildConfig.DEBUG) "/conf/AppCepa.conf" else "/conf/KioskTIDServer.conf"
/** Android config path */
private var appConfigPath: String = if(BuildConfig.DEBUG) "/conf/$appConfigFile" else "/conf/$appConfigFile"
/** VNC config URI */
var configServerUri: Uri = Uri.parse(configServerFqdn + configServerPath)
/** Android config URI */
var appConfigUri: Uri = Uri.parse(configServerFqdn + appConfigPath)
init {
refreshConfigProperties()
SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, s ->
sharedPreferencesFields.forEach(fun (key, items) {
if (items.indexOf(s) != -1) {
when (key) {
"Int" -> initProp(s, sharedPreferences.getString(s,
readInstanceProperty<Int>(this, s).toString()
)!!)
"InetAddress" -> initProp(s, InetAddress.getByName(sharedPreferences.getString(s,
readInstanceProperty<InetAddress>(this, s).hostAddress
)))
"String" -> initProp(s, sharedPreferences.getString(s,
readInstanceProperty<String>(this, s)
)!!)
"Uri" -> initProp(s, Uri.parse(sharedPreferences.getString(s,
readInstanceProperty<Uri>(this, s).toString()
)))
}
}
})
}
}
/** Init prop Integer */
private fun initProp(prop: String, value: Int): Int {
val preferenceValue = preferences.getInt(prop, value)
return if(preferenceValue !== null) preferenceValue else value
}
/** Init prop String */
private fun initProp(prop: String, value: String): String {
val preferenceValue = preferences.getString(prop, value)
return if(preferenceValue !== null) preferenceValue else value
}
/** Init prop Uri */
private fun initProp(prop: String, value: Uri): Uri {
val preferenceValue = Uri.parse(preferences.getString(prop, value.toString()))
return if(preferenceValue !== null) preferenceValue else value
}
/** Init prop Inet */
private fun initProp(prop: String, value: InetAddress): InetAddress {
val preferenceValue = InetAddress.getByName(preferences.getString(prop, value.hostAddress))
return if(preferenceValue !== null) preferenceValue else value
}
/** Set prop Integer */
private fun setProp(prop: String, value: Int) {
preferences.edit().putInt(prop, value).commit()
}
/** Set prop String */
private fun setProp(prop: String, value: String) {
preferences.edit().putString(prop, value).commit()
}
/** Set prop Uri */
private fun setProp(prop: String, value: Uri) {
preferences.edit().putString(prop, value.toString()).commit()
}
/** Set prop Inet */
private fun setProp(prop: String, value: InetAddress) {
preferences.edit().putString(prop, value.hostAddress).commit()
}
/**
* Refresh info or init from sharedPreferences
*/
fun refreshConfigProperties() {
vncArgIp = initProp("vncArgIp", vncArgIp)
vncArgPort = initProp("vncArgPort", vncArgPort)
configPassword = initProp("configPassword", configPassword)
configServerFqdn = initProp("configServerFqdn", configServerFqdn)
configServerPath = initProp("configServerPath", configServerPath)
appConfigPath = initProp("appConfigPath", appConfigPath)
configServerUri = initProp("configServerUri", configServerUri)
appConfigUri = initProp("appConfigUri", appConfigUri)
}
/**
* Generate identifier using SerialIntCaster or fetch from preferences
*/
private fun fetchIdentifier(): String {
val identifierPref: String? = preferences.getString("TID_IDENTIFIER", "NOT_SET")
var identifier = if(identifierPref === null) "NOT_SET" else identifierPref
if (identifier == "NOT_SET")
{
identifier = SerialCaster.encode(
System.currentTimeMillis(),
564646546765768,
11,
"ABCDEFGHJKLMNPQRSTUVWXYZ23456789".toCharArray()
)
preferences.edit().putString("TID_IDENTIFIER", identifier).commit()
}
return identifier
}
/**
* Set Device ip in app info using application context
*/
@SuppressLint("HardwareIds")
fun retrieveDeviceNetworkInfo(applicationContext: Context) {
val connectivityManager = applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
ip = InetAddress.getByName("127.0.0.1")
gateway = InetAddress.getByName("127.0.0.1")
val linkProperties = connectivityManager.getLinkProperties(connectivityManager.activeNetwork)
ip = linkProperties?.linkAddresses?.first {
!it.address.isLoopbackAddress && it.address.isSiteLocalAddress
}?.address
val fetchedGateway = linkProperties?.routes?.first {
val inetGateway = it.gateway
inetGateway !== null && !inetGateway.isLoopbackAddress && inetGateway.isSiteLocalAddress
}?.gateway
if(fetchedGateway !== null) {
gateway = fetchedGateway
}
}
/**
* Load VNC profile (blocked on the first slot)
*/
fun loadProfile(viewModel: HomeViewModel): ServerProfile? {
val profiles = viewModel.getAllProfiles()
val profile = profiles.find { it.ID == 1.toLong() }
if (profile != null) {
return profile
}
return null
}
/**
* Set config Uri
* @throws InvalidUriException If the url is not valid or the file pointing at is not a .conf
*/
fun setConfigUri(newConfigUri: Uri) {
val uriInput = newConfigUri.toString()
if (uriInput.toHttpUrlOrNull() == null || !URLUtil.isValidUrl(uriInput)) {
throw InvalidUriException()
}
var newConfigPath = newConfigUri.path
newConfigPath = if (newConfigPath !== null) newConfigPath else
(if(BuildConfig.DEBUG) "/AppCepa.conf" else "/conf/KioskTIDServer.conf")
val pathSegments: List<String> = Uri.parse(newConfigPath).pathSegments
val fileName = pathSegments.last()
if (fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length) != "conf") {
throw InvalidUriException()
}
// * Save config
configServerFqdn = Uri.parse("${newConfigUri.scheme}://${newConfigUri.host}").toString()
setProp("configServerFqdn", configServerFqdn)
//println(configServerFqdn)
configServerPath = Uri.parse(newConfigPath).toString()
setProp("configServerPath", configServerPath)
//println(configServerPath)
/** VNC config URI */
configServerUri = Uri.parse(configServerFqdn + configServerPath)
setProp("configServerUri", configServerUri)
//println(configServerUri)
/** Android config URI */
appConfigPath = newConfigPath.substring(0, newConfigPath.indexOf(fileName)) + appConfigFile
setProp("appConfigPath", appConfigPath)
//println(appConfigPath)
appConfigUri = Uri.parse(configServerFqdn + appConfigPath)
setProp("appConfigUri", appConfigUri)
//println(appConfigUri)
}
/**
* Set VNC ip port
*/
fun setVncIpPort(newVncArgIp: InetAddress, newVncArgPort: Int) {
vncArgIp = newVncArgIp
setProp("vncArgIp", vncArgIp)
vncArgPort = newVncArgPort
setProp("vncArgPort", vncArgPort)
}
/**
* Assign settings password
* @throws PasswordValidationException
*/
fun setPassword(password: String) {
// * Must contain lower, upper and number min 8 max 30
val regex: String = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?!.* ).{8,30}$"
val p: Pattern = Pattern.compile(regex)
val m: Matcher = p.matcher(password)
if (!m.matches()) {
throw PasswordValidationException()
}
configPassword = password
setProp("configPassword", configPassword)
}
@Suppress("UNCHECKED_CAST")
fun <R> readInstanceProperty(instance: Any, propertyName: String): R {
val property = instance::class.members
// don't cast here to <Any, R>, it would succeed silently
.first { it.name == propertyName } as KProperty1<Any, *>
// force a invalid cast exception if incorrect type here
return property.get(instance) as R
}
}

View file

@ -0,0 +1,22 @@
package com.gaurav.avnc.ui.ldc.lib
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.gaurav.avnc.ui.ldc.KioskActivity
import com.gaurav.avnc.ui.ldc.fragments.KioskHomeFragment
import com.gaurav.avnc.ui.ldc.fragments.KioskInformationFragment
class KioskViewPagerAdapter(kiosk_activity: KioskActivity) : FragmentStateAdapter(kiosk_activity) {
override fun getItemCount(): Int {
return 2
}
override fun createFragment(position: Int): Fragment {
return when(position) {
0 -> KioskHomeFragment()
1 -> KioskInformationFragment()
else -> throw Error("Fragment not Handled $position")
}
}
}

View file

@ -0,0 +1,136 @@
package com.gaurav.avnc.ui.ldc.lib
import android.content.Context
import android.content.Context.CONNECTIVITY_SERVICE
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkRequest
import android.util.Log
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import com.gaurav.avnc.ui.ldc.events.NetworkStateChangedEvent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import java.util.concurrent.atomic.AtomicBoolean
/**
* This class is in charge of listening to the state of the network connection and notifying the
* activity if the state of the connection changes.
* */
class NetworkObserver constructor(
private val context: Context,
private val lifecycle: Lifecycle
) : DefaultLifecycleObserver {
private lateinit var networkCallback: ConnectivityManager.NetworkCallback
private var connectivityManager: ConnectivityManager? = null
private val validNetworks = HashSet<Network>()
private lateinit var job: Job
private lateinit var coroutineScope: CoroutineScope
// State Holder: Indicating either the network is available or not-available
private val _networkAvailableStateFlow: MutableStateFlow<NetworkState> = MutableStateFlow(NetworkState.Available)
val networkAvailableStateFlow
get() = _networkAvailableStateFlow
// ---> This variable can be accessed anytime to get the current state of the network
val isConnected: Boolean
get() = _isConnected.get()
private val _isConnected = AtomicBoolean(false)
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
init()
}
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
registerNetworkCallback()
checkValidNetworks()
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
unregisterNetworkCallback()
}
private fun init() {
// Initialize the connectivity manager instance
connectivityManager = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
}
private fun registerNetworkCallback() {
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
// Observing the network should happen only when the life-cycle is in started state
initCoroutine()
initNetworkMonitoring()
}
}
private fun unregisterNetworkCallback() {
validNetworks.clear()
connectivityManager?.unregisterNetworkCallback(networkCallback)
job.cancel()
}
/**
* Co-Routine used to monitor the connectivity
*/
private fun initCoroutine() {
// Create a job instance
job = Job()
// Provide a co-routine scope
coroutineScope = CoroutineScope(Dispatchers.Default + job)
}
private fun initNetworkMonitoring() {
networkCallback = createNetworkCallback()
val networkRequest = NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_INTERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build()
connectivityManager?.registerNetworkCallback(networkRequest, networkCallback)
}
private fun createNetworkCallback() = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
Log.d("network", "Has new network $network")
validNetworks.add(network)
checkValidNetworks()
EventBus.getDefault().post(NetworkStateChangedEvent())
}
override fun onLost(network: Network) {
Log.d("network", "Remove network $network")
validNetworks.remove(network)
checkValidNetworks()
EventBus.getDefault().post(NetworkStateChangedEvent())
}
}
private fun checkValidNetworks() {
coroutineScope.launch {
_networkAvailableStateFlow.emit(
if (validNetworks.size > 0){
_isConnected.set(true)
NetworkState.Available
} else {
_isConnected.set(false)
NetworkState.UnAvailable
}
)
}
}
}

View file

@ -0,0 +1,5 @@
package com.gaurav.avnc.ui.ldc.lib
enum class NetworkState {
UnAvailable, Available
}

View file

@ -0,0 +1,35 @@
package com.gaurav.avnc.ui.ldc.lib
/**
* Wifi Channels
* https://en.wikipedia.org/wiki/List_of_WLAN_channels
*/
enum class WifiFrequencies() {
/** 802.11ah LOW (860/900 MHz) */
AH_LOW(755),
/** 802.11ah HIGH (860/900 MHz) */
AH_HIGH(755),
/** 802.11b/g/n/ax LOW (2.4Ghz) */
BGNAX_LOW(2400),
/** 802.11b/g/n/ax HIGH (2.4Ghz) */
BGNAX_HIGH(2500),
/** 802.11y LOW (3.65 GHz) */
Y_LOW(3650),
/** 802.11y HIGH (3.65 GHz) */
Y_HIGH(3700),
/** 802.11j LOW (4.9–5.0 GHz) */
J_LOW(4900),
/** 802.11j HIGH (4.9–5.0 GHz) */
J_HIGH(5100),
/** 802.11a/h/n/ac/axLOW (5Ghz) */
AHNACAX_LOW(5100),
/** 802.11a/h/n/ac/ax HIGH (5Ghz) */
AHNACAX_HIGH(5900);
var freq: Int = 0;
// custom constructors
constructor(freq: Int) : this() {
this.freq = freq
}
}

View file

@ -0,0 +1,4 @@
package com.gaurav.avnc.ui.ldc.lib.exceptions
class InvalidIpException: RuntimeException() {
}

View file

@ -0,0 +1,4 @@
package com.gaurav.avnc.ui.ldc.lib.exceptions
class InvalidUriException: RuntimeException() {
}

View file

@ -0,0 +1,4 @@
package com.gaurav.avnc.ui.ldc.lib.exceptions
class PasswordValidationException: RuntimeException() {
}

View file

@ -0,0 +1,4 @@
package com.gaurav.avnc.ui.ldc.lib.exceptions
class PingFailedException: RuntimeException() {
}

View file

@ -90,6 +90,10 @@ class HomeViewModel(app: Application) : BaseViewModel(app) {
}
}
fun getAllProfiles(): List<ServerProfile> {
return serverProfileDao.getList()
}
/**************************************************************************
* Profile editing/creating

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android">
<shape android:shape="rectangle">
<stroke
android:width="1dp"
android:color="#000"/>
<solid android:color="@android:color/transparent" />
</shape>
</inset>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetBottom="-2dp">
<shape android:shape="rectangle">
<stroke
android:width="1dp"
android:color="#000"/>
<solid android:color="@android:color/transparent" />
</shape>
</inset>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="-2dp" android:insetBottom="-2dp">
<shape android:shape="rectangle">
<stroke
android:width="1dp"
android:color="#000" />
<solid android:color="@android:color/transparent" />
</shape>
</inset>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetTop="-2dp" android:insetBottom="-2dp">
<shape android:shape="rectangle">
<stroke
android:width="1dp"
android:color="#000" />
<solid android:color="@android:color/transparent" />
</shape>
</inset>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetTop="-2dp" android:insetBottom="-2dp" android:insetLeft="-2dp">
<shape android:shape="rectangle">
<stroke
android:width="1dp"
android:color="#000" />
<solid android:color="@android:color/transparent" />
</shape>
</inset>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetTop="-2dp">
<shape android:shape="rectangle">
<stroke
android:width="1dp"
android:color="#000"/>
<solid android:color="@android:color/transparent" />
</shape>
</inset>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="-2dp"
android:insetTop="-2dp">
<shape android:shape="rectangle">
<stroke
android:width="1dp"
android:color="#000" />
<solid android:color="@android:color/transparent" />
</shape>
</inset>

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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=".ui.ldc.KioskActivity">
<com.google.android.material.tabs.TabLayout
android:id="@+id/kiosk_tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
app:tabIconTint="#D4021D"
app:tabIndicatorColor="#D4021D"
app:tabTextColor="?android:attr/textColorSecondaryInverseNoDisable">
<com.google.android.material.tabs.TabItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Accueil" />
<com.google.android.material.tabs.TabItem
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Informations" />
</com.google.android.material.tabs.TabLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/kiosk_view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/kiosk_tab_layout" />
</RelativeLayout>

View file

@ -0,0 +1,169 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".ui.ldc.KioskDiagnosticActivity">
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Diagnostique"
android:textColor="#000000"
android:textSize="48sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/btnLineLinearLayout"
style="@style/width_match_parent_max_200"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="64dp"
android:layout_marginRight="20dp"
android:minWidth="280dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout">
<Button
android:id="@+id/btnPing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="1"
android:width="120dp"
android:backgroundTint="#D4021D"
android:text="Ping" />
<Button
android:id="@+id/btnReboot"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="1"
android:width="120dp"
android:backgroundTint="#D4021D"
android:text="Reboot" />
</LinearLayout>
<LinearLayout
android:id="@+id/btnLine2LinearLayout"
style="@style/width_match_parent_max_200"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="280dp"
android:orientation="horizontal"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnLineLinearLayout">
<Button
android:id="@+id/btnGoBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="1"
android:backgroundTint="#D4021D"
android:text="Retour"
android:width="120dp"/>
<Button
android:id="@+id/btnRefresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="1"
android:width="120dp"
android:backgroundTint="#D4021D"
android:text="Rafraichir" />
</LinearLayout>
<LinearLayout
android:id="@+id/wifiStrengthLinearLayout"
style="@style/width_match_parent_max_200"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minWidth="280dp"
android:orientation="horizontal"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnLine2LinearLayout">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Signal"
android:textColor="#000000"
android:textSize="18dp"
android:textStyle="bold" />
<ProgressBar
android:id="@+id/progressBarWifiStrengthPercent"
style="?android:attr/progressBarStyleHorizontal"
android:padding="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView
android:id="@+id/textViewWifiStrengthPercent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0%"
android:textColor="#000000"
android:textSize="18dp"
android:textStyle="bold" />
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/wifiStrengthLinearLayout">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TableLayout
android:id="@+id/tableLayoutWifiRows"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp">
<TextView
android:id="@+id/tableCellMissingPermissions"
android:layout_width="match_parent"
android:layout_height="81dp"
android:padding="2.5dp"
android:text="Chargement.." />
</TableLayout>
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tableRowWifiHeader"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:padding="2.5dp">
<TextView
android:padding="2.5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Réseau"/>
<TextView
android:padding="2.5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Freq"/>
<TextView
android:padding="2.5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="dBm"/>
<TextView
android:padding="2.5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Signal"/>
</TableRow>

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:padding="2.5dp">
<TextView
android:id="@+id/tableCellMissingPermissions"
android:padding="2.5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Utilisez le bouton rafraichir"/>
</TableRow>

View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:padding="2.5dp">
<TextView
android:id="@+id/tableCellSSID"
android:padding="2.5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""/>
<TextView
android:id="@+id/tableCellSignalFreq"
android:padding="2.5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAlignment="center"/>
<TextView
android:id="@+id/tableCellSignal"
android:padding="2.5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAlignment="center"/>
<TextView
android:id="@+id/tableCellSignalPercent"
android:padding="2.5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textAlignment="center"/>
</TableRow>

View file

@ -0,0 +1,154 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".ui.ldc.KioskParameterActivity">
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Paramètres"
android:textColor="#000000"
android:textSize="48sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/configBloc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="64dp"
android:layout_marginRight="20dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Url du serveur de configuration"
android:textAlignment="center"
android:textColor="#000000"
android:textSize="20sp"
android:textStyle="bold" />
<EditText
android:id="@+id/editTextServerUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="2dp"
android:layout_marginRight="20dp"
android:ems="10"
android:inputType="textUri" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="Arguments VNC (IP:Port)"
android:textAlignment="center"
android:textColor="#000000"
android:textSize="20sp"
android:textStyle="bold" />
<LinearLayout
style="@style/width_match_parent_max_200"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:orientation="horizontal">
<EditText
android:id="@+id/editTextIp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_marginLeft="20dp"
android:layout_marginTop="0dp"
android:layout_marginRight="20dp"
android:ems="10"
android:inputType="text|numberSigned" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=":"
android:textAlignment="center"
android:textColor="#000000"
android:textSize="20sp"
android:textStyle="bold" />
<EditText
android:id="@+id/editTextPort"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_marginLeft="20dp"
android:layout_marginTop="0dp"
android:layout_marginRight="20dp"
android:ems="10"
android:inputType="text|numberSigned" />
</LinearLayout>
<LinearLayout
style="@style/width_match_parent_max_200"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:orientation="horizontal">
<Button
android:id="@+id/btnGoBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="1"
android:backgroundTint="#D4021D"
android:text="Retour" />
<Button
android:id="@+id/btnValidateConfig"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="1"
android:backgroundTint="#D4021D"
android:text="Ok" />
</LinearLayout>
<LinearLayout
style="@style/width_match_parent_max_200"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:orientation="horizontal">
<Button
android:id="@+id/btnDiagnostic"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="1"
android:backgroundTint="#D4021D"
android:text="Diagnostique" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".ui.ldc.KioskParameterPasswordActivity">
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Paramètres"
android:textColor="#000000"
android:textSize="48sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/passwordBloc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="64dp"
android:layout_marginRight="20dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Mot de passe requis"
android:textAlignment="center"
android:textColor="#000000"
android:textSize="20sp"
android:textStyle="bold" />
<EditText
android:id="@+id/editTextPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="30dp"
android:layout_marginRight="20dp"
android:ems="10"
android:inputType="textPassword"
android:text="" />
<LinearLayout
style="@style/width_match_parent_max_200"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:orientation="horizontal">
<Button
android:id="@+id/btnGoBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="1"
android:backgroundTint="#D4021D"
android:text="Retour" />
<Button
android:id="@+id/btnSubmitPassword"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="1"
android:backgroundTint="#D4021D"
android:text="Ok" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".ui.ldc.KioskPingActivity">
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Diagnostique"
android:textColor="#000000"
android:textSize="48sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/configBloc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="64dp"
android:layout_marginRight="20dp"
android:minWidth="240dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="FQDN / Adresse Ip"
android:textAlignment="center"
android:textColor="#000000"
android:textSize="20sp"
android:textStyle="bold" />
<EditText
android:id="@+id/editTextPingUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="30dp"
android:layout_marginRight="20dp"
android:ems="10"
android:inputType="textUri" />
<TextView
android:id="@+id/textResult"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:maxWidth="200dp"
android:maxLines="2"
android:text="Succès !"
android:textAlignment="center"
android:textColor="#000000"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="parent" />
<LinearLayout
style="@style/width_match_parent_max_200"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:orientation="horizontal">
<Button
android:id="@+id/btnGoBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="1"
android:backgroundTint="#D4021D"
android:text="Retour" />
<Button
android:id="@+id/btnPing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="1"
android:backgroundTint="#D4021D"
android:text="Ping" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -19,7 +19,7 @@
android:layout_height="80dp"
android:layout_gravity="center"
android:importantForAccessibility="no"
android:src="@mipmap/ic_launcher_round" />
android:src="@mipmap/ic_launcher" />
<TextView
android:layout_width="wrap_content"

View file

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".ui.ldc.fragments.KioskHomeFragment">
<Button
android:id="@+id/btnLoadConfig"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="32dp"
android:backgroundTint="#D4021D"
android:text="Charger la configuration"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout" />
<Button
android:id="@+id/btnVNC"
android:layout_width="285dp"
android:layout_height="78dp"
android:layout_marginStart="16dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="16dp"
android:backgroundTint="#D4021D"
android:onClick="btnStartVncSession"
android:text="Commencer"
android:textSize="34sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnLoadConfig" />
<Button
android:id="@+id/btnParameters"
android:layout_width="215dp"
android:layout_height="65dp"
android:layout_marginTop="5dp"
android:backgroundTint="#D4021D"
android:text="Paramètres"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnVNC" />
<ImageView
android:id="@+id/imageView"
android:layout_width="124dp"
android:layout_height="135dp"
android:layout_marginStart="32dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/logo_ldc" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginTop="12dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView">
<TextView
android:id="@+id/txtMacAddress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Identifiant TID : -"
android:textColor="#000000"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txtIPAddress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Adresse IP : -"
android:textColor="#000000"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txtVNCArgs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Arguments VNC : -"
android:textColor="#000000"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
<ProgressBar
android:id="@+id/progressBar"
style="@android:style/Widget.DeviceDefault.Light.ProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btnParameters" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".ui.ldc.fragments.KioskInformationFragment">
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Informations"
android:textColor="#000000"
android:textSize="48sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/infoLinearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:layout_marginLeft="14dp"
android:layout_marginRight="14dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="fill"
android:background="@drawable/bg_rect_no_bottom"
android:padding="10dp"
android:text="TEXT"
android:textColor="#000000"
android:textSize="16sp"
android:textStyle="bold"
android:lineHeight="18sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="fill"
android:background="@drawable/bg_rect_no_bottom"
android:padding="10dp"
android:text="TEXT"
android:textColor="#000000"
android:textSize="16sp"
android:textStyle="bold"
android:lineHeight="18sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="fill"
android:background="@drawable/bg_rect_no_bottom"
android:padding="10dp"
android:text="TEXT"
android:textColor="#000000"
android:textSize="16sp"
android:textStyle="bold"
android:lineHeight="18sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="fill"
android:background="@drawable/bg_rect_no_bottom"
android:padding="10dp"
android:text="TEXT"
android:textColor="#000000"
android:textSize="16sp"
android:textStyle="bold"
android:lineHeight="18sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="fill"
android:background="@drawable/bg_rect_no_bottom"
android:padding="10dp"
android:text="TEXT"
android:textColor="#000000"
android:textSize="16sp"
android:textStyle="bold"
android:lineHeight="18sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="fill"
android:background="@drawable/bg_rect_no_bottom"
android:padding="10dp"
android:text="TEXT"
android:textColor="#000000"
android:textSize="16sp"
android:textStyle="bold"
android:lineHeight="18sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="fill"
android:background="@drawable/bg_rect"
android:padding="10dp"
android:text="TEXT"
android:textColor="#000000"
android:textSize="16sp"
android:textStyle="bold"
android:lineHeight="18sp"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,12 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2020 Gaurav Ujjwal.
~
~ SPDX-License-Identifier: GPL-3.0-or-later
~
~ See COPYING.txt for more details.
-->
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -1,12 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2020 Gaurav Ujjwal.
~
~ SPDX-License-Identifier: GPL-3.0-or-later
~
~ See COPYING.txt for more details.
-->
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View file

@ -7,7 +7,7 @@
-->
<resources>
<string name="app_name" translatable="false">AVNC</string>
<string name="app_name" translatable="false">Kiosk LDC</string>
<string name="title_add_server_profile">Add server</string>
<string name="title_edit_server_profile">Edit server</string>
@ -184,4 +184,6 @@
<string name="pref_experimental_toolbar_swipe_close_summary">During a remote session, you can swipe to close currently open Toolbar</string>
<string name="pref_experimental_immersive_mode">Full immersive mode</string>
<string name="pref_experimental_immersive_mode_summary">Hides all system bars in fullscreen mode</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
</resources>

View file

@ -101,4 +101,8 @@
<style name="VirtualKey.Toggle">
<item name="android:background">@drawable/bg_toggle_button</item>
</style>
<style name="width_match_parent_max_200">
<item name="android:layout_width">200dp</item>
</style>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localtid</domain>
</domain-config>
</network-security-config>

View file

@ -1,5 +1,6 @@
buildscript {
ext.kotlin_version = '1.4.31'
//ext.kotlin_version = '1.4.31'
ext.kotlin_version = '1.6.0'
repositories {
google()
jcenter()

View file

@ -1,2 +1,2 @@
include ':app'
rootProject.name = "AVNC"
rootProject.name = "AVNC"