diff --git a/.circleci/config.yml b/.circleci/config.yml
index 732b94361..406776fec 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -52,7 +52,7 @@ reference:
android_config: &android_config
working_directory: *workspace
docker:
- - image: circleci/android:api-28-ndk
+ - image: circleci/android@sha256:fa7a00c75f4b28cc4f2a15a382fb76e830d62a77efd1a3f8549f7f5fdad4ca44
environment:
TERM: dumb
# Limit JVM heap size to prevent exceeding container memory
@@ -345,4 +345,4 @@ workflows:
requires:
- test_unit
- test_instrumented
- - build_production_release
\ No newline at end of file
+ - build_production_release
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 350ebf81c..7d2bafd16 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,7 +6,7 @@ These are intended as just that: guidelines, so use your best judgement when sub
All contributions must follow the UserLAnd [Code of Conduct](https://github.com/CypherpunkArmory/UserLAnd/blob/master/CODE_OF_CONDUCT.md).
## Connect with us
-If you have any questions please join us on our [slack](https://communityinviter.com/apps/userlandtech/userland) #contributors channel
+Please talk with us here in the form of a PR or an issue.
## Architecture
We follow the MVVM-C architecture in UserLAnd. UI updates should exist exclusively within XML and be inflated exclusively from
diff --git a/README.md b/README.md
index 0663b1f0b..c7ec0b792 100644
--- a/README.md
+++ b/README.md
@@ -8,9 +8,9 @@ Features:
* Install and uninstall like a regular app.
* No root required.
-[](https://f-droid.org/packages/tech.ula/)
+[](https://f-droid.org/packages/tech.ula)
[](https://play.google.com/store/apps/details?id=tech.ula)
@@ -18,7 +18,6 @@ Features:
## Have a bug report or a feature request?
You can see our templates by visiting our [issue center](https://github.com/CypherpunkArmory/UserLAnd/issues).
-You can also chat with us on [slack](https://communityinviter.com/apps/userlandtech/userland).
## Want to contribute?
See our [CONTRIBUTING](https://github.com/CypherpunkArmory/UserLAnd/blob/master/CONTRIBUTING.md) document.
diff --git a/app/build.gradle b/app/build.gradle
index 9d3da64fb..5110da5e2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -24,7 +24,7 @@ android {
minSdkVersion 21
targetSdkVersion 29
versionCode vcode
- versionName "2.6.2"
+ versionName "2.6.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
@@ -198,7 +198,7 @@ ext.architectures = ["armeabi-v7a", "arm64-v8a", "x86", "x86_64"]
ext.libDir = "$project.projectDir/src/main/jniLibs"
task downloadAssets(type: Download) {
- def assetVersion = "v1.0.1"
+ def assetVersion = "v1.1.0"
def baseUrl = "https://github.com/CypherpunkArmory/UserLAnd-Assets-Support/releases/download/$assetVersion"
for (arch in architectures) {
src "$baseUrl/$arch-assets.zip"
@@ -222,6 +222,7 @@ task fetchAssets(dependsOn: downloadAssets) {
// Lib files must start with 'lib' and end with '.so.'
rename '(.*)', 'lib_$1.so'
}
+ new File("$libDir/$arch","lib_arch.so").text = "$arch"
}
}
}
@@ -280,7 +281,6 @@ dependencies {
def mockito_version = '2.23.0'
def mockito_kotlin_version = '2.1.0'
def core_testing_version = '2.0.0-beta01'
- def espresso_version = '3.2.0'
def androidx_test_version = '1.2.0'
def androidx_test_ext_version = '1.1.0'
@@ -296,6 +296,7 @@ dependencies {
androidTestImplementation "androidx.test:rules:$androidx_test_version"
androidTestImplementation "androidx.test.ext:junit:$androidx_test_ext_version"
// Barista packages espresso-core and espresso-contrib
+ androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0'
androidTestImplementation('com.schibsted.spain:barista:3.1.0') {
exclude group: 'com.android.support'
exclude group: 'org.jetbrains.kotlin'
diff --git a/app/src/androidTest/java/tech/ula/ui/MainActivityTest.kt b/app/src/androidTest/java/tech/ula/ui/MainActivityTest.kt
index f011f9274..effa68c65 100644
--- a/app/src/androidTest/java/tech/ula/ui/MainActivityTest.kt
+++ b/app/src/androidTest/java/tech/ula/ui/MainActivityTest.kt
@@ -1,10 +1,14 @@
package tech.ula.ui
import android.Manifest
+import android.content.Intent
+import android.net.Uri
import androidx.test.espresso.Espresso
+import androidx.test.espresso.intent.rule.IntentsTestRule
+import androidx.test.espresso.intent.Intents.intended
+import androidx.test.espresso.intent.matcher.IntentMatchers.* // ktlint-disable no-wildcard-imports
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
-import androidx.test.rule.ActivityTestRule
import androidx.test.rule.GrantPermissionRule
import com.schibsted.spain.barista.assertion.BaristaListAssertions.assertDisplayedAtPosition
import com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertNotDisplayed
@@ -27,7 +31,7 @@ import java.io.File
class MainActivityTest {
@get:Rule
- val activityRule = ActivityTestRule(MainActivity::class.java)
+ val intentTestRule = IntentsTestRule(MainActivity::class.java)
// Permissions are granted automatically by firebase, so to keep parity we skip that step
// locally as well.
@@ -47,12 +51,12 @@ class MainActivityTest {
@Before
fun setup() {
- activity = activityRule.activity
+ activity = intentTestRule.activity
homeDirectory = File(activity.filesDir, homeLocation)
}
@Test
- fun testHappyPath() {
+ fun test_ssh_session_can_be_started() {
R.id.app_list_fragment.shortWaitForDisplay()
R.id.swipe_refresh.waitForRefresh(activity)
@@ -121,6 +125,58 @@ class MainActivityTest {
R.id.terminal_view.shortWaitForDisplay()
}
+ @Test
+ fun test_vnc_session_can_be_started() {
+ R.id.app_list_fragment.shortWaitForDisplay()
+
+ R.id.swipe_refresh.waitForRefresh(activity)
+
+ // Click alpine
+ assertDisplayedAtPosition(R.id.list_apps, 0, R.id.apps_name, appName)
+ clickListItem(R.id.list_apps, 0)
+
+ // Set filesystem credentials
+ R.string.filesystem_credentials_reasoning.waitForDisplay()
+ writeTo(R.id.text_input_username, username)
+ writeTo(R.id.text_input_password, sshPassword)
+ writeTo(R.id.text_input_vnc_password, vncPassword)
+ clickDialogPositiveButton()
+
+ // Set session type to vnc
+ R.string.prompt_app_connection_type_preference.shortWaitForDisplay()
+ clickRadioButtonItem(R.id.radio_apps_service_type_preference, R.id.vnc_radio_button)
+ clickDialogPositiveButton()
+
+ // Wait for progress dialog to complete
+ R.id.progress_bar_session_list.shortWaitForDisplay()
+ R.string.progress_downloading.longWaitForDisplay()
+ R.string.progress_copying_downloads.extraLongWaitForDisplay()
+ R.string.progress_verifying_assets.waitForDisplay()
+ R.string.progress_setting_up_filesystem.waitForDisplay()
+ R.string.progress_starting.longWaitForDisplay()
+ Thread.sleep(10000)
+
+ val clientIntent = Intent()
+ clientIntent.action = Intent.ACTION_VIEW
+ clientIntent.type = "application/vnd.vnc"
+ clientIntent.data = Uri.parse("vnc://127.0.0.1:5951/?VncUsername=$username&VncPassword=$vncPassword")
+ clientIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ val packageManager = activity.packageManager
+ val activities = packageManager.queryIntentActivities(clientIntent, 0)
+ if (activities.size > 0) {
+ // Match case where bVNC is present, e.g. test is running on personal device
+ intended(hasAction(Intent.ACTION_VIEW))
+ intended(hasData(Uri.parse("vnc://127.0.0.1:5951/?VncUsername=$username&VncPassword=$vncPassword")))
+ intended(hasFlag(Intent.FLAG_ACTIVITY_NEW_TASK))
+ } else {
+ // Match case where bVNC is not present on device, e.g. firebase
+ val packageName = "com.iiordanov.freebVNC"
+ intended(hasAction(Intent.ACTION_VIEW))
+ intended(hasData(Uri.parse("market://details?id=$packageName")))
+ intended(hasFlag(Intent.FLAG_ACTIVITY_NEW_TASK))
+ }
+ }
+
private fun doHappyPathTestScript(): List {
val startedFile = File(homeDirectory, "test.scriptStarted")
val updatedFile = File(homeDirectory, "test.apkUpdated")
diff --git a/app/src/main/java/tech/ula/MainActivity.kt b/app/src/main/java/tech/ula/MainActivity.kt
index 1b45e0ed7..32f2f86c1 100644
--- a/app/src/main/java/tech/ula/MainActivity.kt
+++ b/app/src/main/java/tech/ula/MainActivity.kt
@@ -40,6 +40,7 @@ import kotlinx.coroutines.launch
import tech.ula.model.entities.App
import tech.ula.model.entities.ServiceType
import tech.ula.model.entities.Session
+import tech.ula.model.remote.GithubApiClient
import tech.ula.model.repositories.AssetRepository
import tech.ula.model.repositories.UlaDatabase
import tech.ula.model.state.* // ktlint-disable no-wildcard-imports
@@ -117,7 +118,8 @@ class MainActivity : AppCompatActivity(), SessionListFragment.SessionSelection,
val ulaDatabase = UlaDatabase.getInstance(this)
val assetPreferences = AssetPreferences(this)
- val assetRepository = AssetRepository(filesDir.path, assetPreferences)
+ val githubApiClient = GithubApiClient(ulaFiles)
+ val assetRepository = AssetRepository(filesDir.path, assetPreferences, githubApiClient)
val filesystemManager = FilesystemManager(ulaFiles, busyboxExecutor)
val storageCalculator = StorageCalculator(StatFs(filesDir.path))
@@ -126,7 +128,7 @@ class MainActivity : AppCompatActivity(), SessionListFragment.SessionSelection,
val downloadManagerWrapper = DownloadManagerWrapper(downloadManager)
val assetDownloader = AssetDownloader(assetPreferences, downloadManagerWrapper, ulaFiles)
- val appsStartupFsm = AppsStartupFsm(ulaDatabase, filesystemManager)
+ val appsStartupFsm = AppsStartupFsm(ulaDatabase, filesystemManager, ulaFiles)
val sessionStartupFsm = SessionStartupFsm(ulaDatabase, assetRepository, filesystemManager, assetDownloader, storageCalculator)
ViewModelProviders.of(this, MainActivityViewModelFactory(appsStartupFsm, sessionStartupFsm))
.get(MainActivityViewModel::class.java)
diff --git a/app/src/main/java/tech/ula/ServerService.kt b/app/src/main/java/tech/ula/ServerService.kt
index 7ba657a14..c19db306d 100644
--- a/app/src/main/java/tech/ula/ServerService.kt
+++ b/app/src/main/java/tech/ula/ServerService.kt
@@ -6,17 +6,19 @@ import android.content.Intent
import android.net.Uri
import android.os.IBinder
import androidx.localbroadcastmanager.content.LocalBroadcastManager
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.* // ktlint-disable no-wildcard-imports
import tech.ula.model.entities.App
import tech.ula.model.entities.ServiceType
import tech.ula.model.repositories.UlaDatabase
import tech.ula.model.entities.Session
import tech.ula.utils.* // ktlint-disable no-wildcard-imports
+import kotlin.coroutines.CoroutineContext
-class ServerService : Service() {
+class ServerService : Service(), CoroutineScope {
+
+ private val job = Job()
+ override val coroutineContext: CoroutineContext
+ get() = Dispatchers.Default + job
companion object {
const val SERVER_SERVICE_RESULT: String = "tech.ula.ServerService.RESULT"
@@ -53,9 +55,8 @@ class ServerService : Service() {
when (intent?.getStringExtra("type")) {
"start" -> {
- val coroutineScope = CoroutineScope(Dispatchers.Default)
val session: Session = intent.getParcelableExtra("session")!!
- coroutineScope.launch { startSession(session) }
+ this.launch { startSession(session) }
}
"stopApp" -> {
val app: App = intent.getParcelableExtra("app")!!
@@ -87,10 +88,18 @@ class ServerService : Service() {
// to clean up when app is swiped away.
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
+ // Redundancy to ensure no hanging processes, given broad device spectrum.
+ this.coroutineContext.cancel()
stopForeground(true)
stopSelf()
}
+ override fun onDestroy() {
+ super.onDestroy()
+ // Redundancy to ensure no hanging processes, given broad device spectrum.
+ this.coroutineContext.cancel()
+ }
+
private fun removeSession(session: Session) {
activeSessions.remove(session.pid)
if (activeSessions.isEmpty()) {
@@ -145,8 +154,8 @@ class ServerService : Service() {
private fun startSshClient(session: Session) {
val connectBotIntent = Intent()
- connectBotIntent.action = "android.intent.action.VIEW"
- connectBotIntent.data = Uri.parse("ssh://${session.username}@localhost:${session.port}/#userland")
+ connectBotIntent.action = Intent.ACTION_VIEW
+ connectBotIntent.data = Uri.parse("ssh://${session.username}@localhost:2022/#userland")
connectBotIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(connectBotIntent)
@@ -154,7 +163,7 @@ class ServerService : Service() {
private fun startVncClient(session: Session, packageName: String) {
val bVncIntent = Intent()
- bVncIntent.action = "android.intent.action.VIEW"
+ bVncIntent.action = Intent.ACTION_VIEW
bVncIntent.type = "application/vnd.vnc"
bVncIntent.data = Uri.parse("vnc://127.0.0.1:5951/?VncUsername=${session.username}&VncPassword=${session.vncPassword}")
bVncIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
diff --git a/app/src/main/java/tech/ula/model/entities/Session.kt b/app/src/main/java/tech/ula/model/entities/Session.kt
index dcfeb9ed1..485df36ba 100644
--- a/app/src/main/java/tech/ula/model/entities/Session.kt
+++ b/app/src/main/java/tech/ula/model/entities/Session.kt
@@ -77,7 +77,7 @@ data class Session(
var password: String = "",
var vncPassword: String = "",
var serviceType: ServiceType = ServiceType.Unselected,
- var port: Long = 2022,
+ var port: Long = 2022, // TODO This can be removed. Any eventual port managing should be done at a high er abstraction.
var pid: Long = 0,
var geometry: String = "",
val isAppsSession: Boolean = false
diff --git a/app/src/main/java/tech/ula/model/remote/GithubApiClient.kt b/app/src/main/java/tech/ula/model/remote/GithubApiClient.kt
index 19b8821a2..5907eec3e 100644
--- a/app/src/main/java/tech/ula/model/remote/GithubApiClient.kt
+++ b/app/src/main/java/tech/ula/model/remote/GithubApiClient.kt
@@ -7,9 +7,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
-import tech.ula.utils.DeviceArchitecture
import tech.ula.utils.Logger
import tech.ula.utils.SentryLogger
+import tech.ula.utils.UlaFiles
import java.io.IOException
import java.net.UnknownHostException
@@ -20,7 +20,7 @@ class UrlProvider {
}
class GithubApiClient(
- private val deviceArchitecture: DeviceArchitecture = DeviceArchitecture(),
+ private val ulaFiles: UlaFiles,
private val urlProvider: UrlProvider = UrlProvider(),
private val logger: Logger = SentryLogger()
) {
@@ -39,7 +39,7 @@ class GithubApiClient(
suspend fun getAssetsListDownloadUrl(repo: String): String = withContext(Dispatchers.IO) {
val result = latestResults[repo] ?: queryLatestRelease(repo)
- return@withContext result.assets.find { it.name == "${deviceArchitecture.getArchType()}-assets.txt" }!!.downloadUrl
+ return@withContext result.assets.find { it.name == "${ulaFiles.getArchType()}-assets.txt" }!!.downloadUrl
}
@Throws(IOException::class)
@@ -52,7 +52,7 @@ class GithubApiClient(
@Throws(IOException::class)
suspend fun getAssetEndpoint(assetType: String, repo: String): String = withContext(Dispatchers.IO) {
val result = latestResults[repo] ?: queryLatestRelease(repo)
- val assetName = "${deviceArchitecture.getArchType()}-$assetType"
+ val assetName = "${ulaFiles.getArchType()}-$assetType"
return@withContext result.assets.find { it.name == assetName }!!.downloadUrl
}
diff --git a/app/src/main/java/tech/ula/model/repositories/AssetRepository.kt b/app/src/main/java/tech/ula/model/repositories/AssetRepository.kt
index cd4a5e97b..1a424c61a 100644
--- a/app/src/main/java/tech/ula/model/repositories/AssetRepository.kt
+++ b/app/src/main/java/tech/ula/model/repositories/AssetRepository.kt
@@ -24,7 +24,7 @@ data class DownloadMetadata(
class AssetRepository(
private val applicationFilesDirPath: String,
private val assetPreferences: AssetPreferences,
- private val githubApiClient: GithubApiClient = GithubApiClient(),
+ private val githubApiClient: GithubApiClient,
private val httpStream: HttpStream = HttpStream(),
private val logger: Logger = SentryLogger()
) {
diff --git a/app/src/main/java/tech/ula/model/state/AppsStartupFsm.kt b/app/src/main/java/tech/ula/model/state/AppsStartupFsm.kt
index 777e66d03..f7e0dd956 100644
--- a/app/src/main/java/tech/ula/model/state/AppsStartupFsm.kt
+++ b/app/src/main/java/tech/ula/model/state/AppsStartupFsm.kt
@@ -16,7 +16,7 @@ import tech.ula.utils.* // ktlint-disable no-wildcard-imports
class AppsStartupFsm(
ulaDatabase: UlaDatabase,
private val filesystemManager: FilesystemManager,
- private val deviceArchitecture: DeviceArchitecture = DeviceArchitecture(),
+ private val ulaFiles: UlaFiles,
private val logger: Logger = SentryLogger()
) {
@@ -114,7 +114,6 @@ class AppsStartupFsm(
private suspend fun setServiceType(appSession: Session, serviceType: ServiceType) = withContext(Dispatchers.IO) {
appSession.serviceType = serviceType
- appSession.port = if (serviceType == ServiceType.Ssh) 2022 else 51
sessionDao.updateSession(appSession)
state.postValue(AppHasServiceTypeSet)
}
@@ -124,7 +123,7 @@ class AppsStartupFsm(
val potentialAppFilesystem = filesystemDao.findAppsFilesystemByType(app.filesystemRequired)
if (potentialAppFilesystem.isEmpty()) {
- val deviceArchitecture = deviceArchitecture.getArchType()
+ val deviceArchitecture = ulaFiles.getArchType()
val fsToInsert = Filesystem(0, name = "apps", archType = deviceArchitecture,
distributionType = app.filesystemRequired, isAppsFilesystem = true)
filesystemDao.insertFilesystem(fsToInsert)
@@ -157,7 +156,6 @@ class AppsStartupFsm(
state.postValue(SyncingDatabaseEntries)
appSession.filesystemId = appsFilesystem.id
appSession.filesystemName = appsFilesystem.name
- appSession.port = if (appSession.serviceType is ServiceType.Ssh) 2022 else 51
appSession.username = appsFilesystem.defaultUsername
appSession.password = appsFilesystem.defaultPassword
appSession.vncPassword = appsFilesystem.defaultVncPassword
diff --git a/app/src/main/java/tech/ula/ui/FilesystemEditFragment.kt b/app/src/main/java/tech/ula/ui/FilesystemEditFragment.kt
index ea6b1dc9a..a227c585a 100644
--- a/app/src/main/java/tech/ula/ui/FilesystemEditFragment.kt
+++ b/app/src/main/java/tech/ula/ui/FilesystemEditFragment.kt
@@ -24,9 +24,9 @@ import kotlinx.android.synthetic.main.frag_filesystem_edit.*
import tech.ula.MainActivity
import tech.ula.R
import tech.ula.model.repositories.UlaDatabase
-import tech.ula.utils.DeviceArchitecture
import tech.ula.utils.PermissionHandler
import tech.ula.utils.CredentialValidator
+import tech.ula.utils.UlaFiles
import tech.ula.utils.preferences.AppsPreferences
import tech.ula.viewmodel.FilesystemImportStatus
import tech.ula.viewmodel.ImportSuccess
@@ -232,12 +232,8 @@ class FilesystemEditFragment : Fragment() {
filesystemEditViewModel.updateFilesystem(filesystem)
navController.popBackStack()
} else {
- try {
- filesystem.archType = DeviceArchitecture().getArchType()
- } catch (err: Exception) {
- Toast.makeText(activityContext, R.string.no_supported_architecture, Toast.LENGTH_LONG).show()
- return true
- }
+ val ulaFiles = UlaFiles(activityContext, activityContext.applicationInfo.nativeLibraryDir)
+ filesystem.archType = ulaFiles.getArchType()
if (filesystem.isCreatedFromBackup) {
filesystemEditViewModel.insertFilesystemFromBackup(activityContext.contentResolver, filesystem, activityContext.filesDir)
} else {
diff --git a/app/src/main/java/tech/ula/ui/SessionEditFragment.kt b/app/src/main/java/tech/ula/ui/SessionEditFragment.kt
index d26823d10..34d4bae59 100644
--- a/app/src/main/java/tech/ula/ui/SessionEditFragment.kt
+++ b/app/src/main/java/tech/ula/ui/SessionEditFragment.kt
@@ -153,7 +153,6 @@ class SessionEditFragment : Fragment() {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val selectedServiceType = parent?.getItemAtPosition(position).toString().toServiceType()
session.serviceType = selectedServiceType
- session.port = getDefaultServicePort(selectedServiceType)
}
}
diff --git a/app/src/main/java/tech/ula/utils/DeviceArchitecture.kt b/app/src/main/java/tech/ula/utils/DeviceArchitecture.kt
deleted file mode 100644
index a655c3b47..000000000
--- a/app/src/main/java/tech/ula/utils/DeviceArchitecture.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-package tech.ula.utils
-
-import android.os.Build
-
-class DeviceArchitecture {
- fun getArchType(): String {
- val supportedABIS = this.getSupportedAbis()
- .map {
- translateABI(it)
- }
- .filter {
- isSupported(it)
- }
- return if (supportedABIS.size == 1 && supportedABIS[0] == "") {
- val exception = IllegalStateException("No supported ABI!")
- SentryLogger().addExceptionBreadcrumb(exception)
- throw exception
- } else {
- supportedABIS[0]
- }
- }
-
- private fun getSupportedAbis(): Array {
- return Build.SUPPORTED_ABIS
- }
-
- private fun isSupported(abi: String): Boolean {
- val supportedABIs = listOf("arm64", "arm", "x86_64", "x86")
- return supportedABIs.contains(abi)
- }
-
- private fun translateABI(abi: String): String {
- return when (abi) {
- "arm64-v8a" -> "arm64"
- "armeabi-v7a" -> "arm"
- "x86_64" -> "x86_64"
- "x86" -> "x86"
- else -> ""
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/tech/ula/utils/LocalServerManager.kt b/app/src/main/java/tech/ula/utils/LocalServerManager.kt
index 50a94555d..5c9802da7 100644
--- a/app/src/main/java/tech/ula/utils/LocalServerManager.kt
+++ b/app/src/main/java/tech/ula/utils/LocalServerManager.kt
@@ -10,6 +10,8 @@ class LocalServerManager(
private val logger: Logger = SentryLogger()
) {
+ private val vncDisplayNumber = 51
+
fun Process.pid(): Long {
return this.toString()
.substringAfter("pid=")
@@ -18,29 +20,6 @@ class LocalServerManager(
.trim().toLong()
}
- private fun Session.pidRelativeFilePath(): String {
- return when (this.serviceType) {
- ServiceType.Ssh -> "/run/dropbear.pid"
- ServiceType.Vnc -> "/home/${this.username}/.vnc/localhost:${this.port}.pid"
- ServiceType.Xsdl -> "/tmp/xsdl.pidfile"
- else -> "error"
- }
- }
-
- private fun Session.pidFilePath(): String {
- return "$applicationFilesDirPath/${this.filesystemId}${this.pidRelativeFilePath()}"
- }
-
- fun Session.pid(): Long {
- val pidFile = File(this.pidFilePath())
- if (!pidFile.exists()) return -1
- return try {
- pidFile.readText().trim().toLong()
- } catch (e: Exception) {
- -1
- }
- }
-
fun startServer(session: Session): Long {
return when (session.serviceType) {
ServiceType.Ssh -> startSSHServer(session)
@@ -50,6 +29,34 @@ class LocalServerManager(
}
}
+ fun stopService(session: Session) {
+ val command = "support/killProcTree.sh ${session.pid} ${session.serverPid()}"
+ val result = busyboxExecutor.executeScript(command)
+ if (result is FailedExecution) {
+ val details = "func: stopService err: ${result.reason}"
+ val breadcrumb = UlaBreadcrumb("LocalServerManager", BreadcrumbType.RuntimeError, details)
+ logger.addBreadcrumb(breadcrumb)
+ }
+ }
+
+ fun isServerRunning(session: Session): Boolean {
+ val command = "support/isServerInProcTree.sh ${session.serverPid()}"
+ // The server itself is run by a third-party, so we can consider this to always be true.
+ // The third-party app is responsible for handling errors starting their server.
+ if (session.serviceType == ServiceType.Xsdl) return true
+ val result = busyboxExecutor.executeScript(command)
+ return when (result) {
+ is SuccessfulExecution -> true
+ is FailedExecution -> {
+ val details = "func: isServerRunning err: ${result.reason}"
+ val breadcrumb = UlaBreadcrumb("LocalServerManager", BreadcrumbType.RuntimeError, details)
+ logger.addBreadcrumb(breadcrumb)
+ false
+ }
+ else -> false
+ }
+ }
+
private fun deletePidFile(session: Session) {
val pidFile = File(session.pidFilePath())
if (pidFile.exists()) pidFile.delete()
@@ -123,31 +130,26 @@ class LocalServerManager(
}
}
- fun stopService(session: Session) {
- val command = "support/killProcTree.sh ${session.pid} ${session.pid()}"
- val result = busyboxExecutor.executeScript(command)
- if (result is FailedExecution) {
- val details = "func: stopService err: ${result.reason}"
- val breadcrumb = UlaBreadcrumb("LocalServerManager", BreadcrumbType.RuntimeError, details)
- logger.addBreadcrumb(breadcrumb)
+ private fun Session.pidRelativeFilePath(): String {
+ return when (this.serviceType) {
+ ServiceType.Ssh -> "/run/dropbear.pid"
+ ServiceType.Vnc -> "/home/${this.username}/.vnc/localhost:$vncDisplayNumber.pid"
+ ServiceType.Xsdl -> "/tmp/xsdl.pidfile"
+ else -> "error"
}
}
- fun isServerRunning(session: Session): Boolean {
- val command = "support/isServerInProcTree.sh ${session.pid()}"
- // The server itself is run by a third-party, so we can consider this to always be true.
- // The third-party app is responsible for handling errors starting their server.
- if (session.serviceType == ServiceType.Xsdl) return true
- val result = busyboxExecutor.executeScript(command)
- return when (result) {
- is SuccessfulExecution -> true
- is FailedExecution -> {
- val details = "func: isServerRunning err: ${result.reason}"
- val breadcrumb = UlaBreadcrumb("LocalServerManager", BreadcrumbType.RuntimeError, details)
- logger.addBreadcrumb(breadcrumb)
- false
- }
- else -> false
+ private fun Session.pidFilePath(): String {
+ return "$applicationFilesDirPath/${this.filesystemId}${this.pidRelativeFilePath()}"
+ }
+
+ private fun Session.serverPid(): Long {
+ val pidFile = File(this.pidFilePath())
+ if (!pidFile.exists()) return -1
+ return try {
+ pidFile.readText().trim().toLong()
+ } catch (e: Exception) {
+ -1
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/tech/ula/utils/UlaFiles.kt b/app/src/main/java/tech/ula/utils/UlaFiles.kt
index d1da0eba6..c68d6d9eb 100644
--- a/app/src/main/java/tech/ula/utils/UlaFiles.kt
+++ b/app/src/main/java/tech/ula/utils/UlaFiles.kt
@@ -71,6 +71,21 @@ class UlaFiles(
symlinker.createSymlink(libFile.path, linkFile.path)
}
}
+
+ fun getArchType(): String {
+ val usedABI = File(libDir, "lib_arch.so").readText()
+ return translateABI(usedABI)
+ }
+
+ private fun translateABI(abi: String): String {
+ return when (abi) {
+ "arm64-v8a" -> "arm64"
+ "armeabi-v7a" -> "arm"
+ "x86_64" -> "x86_64"
+ "x86" -> "x86"
+ else -> ""
+ }
+ }
}
class Symlinker {
diff --git a/app/src/main/java/tech/ula/viewmodel/AppDetailsViewModel.kt b/app/src/main/java/tech/ula/viewmodel/AppDetailsViewModel.kt
index cf03f2248..90b8cb85e 100644
--- a/app/src/main/java/tech/ula/viewmodel/AppDetailsViewModel.kt
+++ b/app/src/main/java/tech/ula/viewmodel/AppDetailsViewModel.kt
@@ -105,7 +105,6 @@ class AppDetailsViewModel(private val sessionDao: SessionDao, private val appDet
if (appSession == null) return@launch
appSession.serviceType = selectedServiceType
- appSession.port = if (selectedServiceType == ServiceType.Vnc) 51 else 2022
this.launch {
withContext(Dispatchers.IO) {
sessionDao.updateSession(appSession)
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index db5183ee5..cd2332716 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -179,7 +179,6 @@
الجلسات تتطلب اسمًا فريدًا!
تتطلب أنظمة الملفات اسمًا فريدًا!
UserLAnd يدعم حاليا فقط جلسة واحدة نشطة!
- لا يدعم UserLAnd بنية الأجهزة حاليًا.
سجل تصحيح حذف أو غير موجود!
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 18ed06c21..7d385109d 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -173,7 +173,6 @@
¡Las sesiones requieren un nombre único!
¡Los sistemas de archivos requieren un nombre único!
¡UserLAnd por ahora sólo soporta una sesión activa!
- UserLAnd por ahora no soporta la arquitectura de tu dispositivo.
¡El archivo de registro de depuración fue eliminado o es inexistente!
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index 218293cce..1d2acae4e 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -123,7 +123,6 @@
نام نشستها باید یکتا باشد!
نام فایلسیستمها باید یکتا باشد!
یوزرلند در حال حاضر تنها از یک نشست فعال پشتیبانی میکند!
- یوزرلند در حال حاضر از معماری دستگاه شما پیشتیبانی نمیکند.
لاگ دیباگ یا پاک شده و یا وجود ندارد!
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 3ded64cbd..ed9dfd017 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -123,7 +123,6 @@
Les Sessions ont besoin d\'un nom unique !
Les Systèmes de fichiers ont besoin d\'un nom unique !
Actuellement UserLAnd ne prend en charge qu’une seule session active !
- L’architecture de votre dispositif n’est pas actuellement prise en charge par UserLAnd.
Le journal de débogage a été supprimé ou n’existe pas !
diff --git a/app/src/main/res/values-jp/strings.xml b/app/src/main/res/values-jp/strings.xml
index e5d6edab8..836c2cc8d 100644
--- a/app/src/main/res/values-jp/strings.xml
+++ b/app/src/main/res/values-jp/strings.xml
@@ -123,7 +123,6 @@
他のセッションと同じ名前は使用できません。
他のファイルシステムと同じ名前は使用できません。
UserLAnd は今の所1個しかセッションを立ち上げられません!
- UserLAnd は現在、ご使用の端末のアーキテクチャに対応していません。
デバッグ情報のログが削除されたか、存在していません!
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index dfe13f9a0..368145241 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -200,7 +200,6 @@
Sessões exigem um nome único!
Sistemas de Arquivos exigem um nome único!
UserLAnd atualmente suporta apenas uma única sessão ativa!
- O UserLAnd não suporta atualmente a arquitetura de seus dispositivo.
Log de depuração excluído ou não existe!
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index f3bdcac0b..17070dae6 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -165,7 +165,6 @@
Сессии должны иметь различные имена!
Файловые системы должны иметь различные имена!
На текущий момент UserLAnd поддерживает лишь единственную активную сессию!
- На текущий момент UserLAnd не поддерживает архитектуру вашего устройства.
Журнал отладки удалён или не существует!
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 5055fc8f4..40a086e39 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -92,7 +92,6 @@
此会话需要一个特殊的名字!
文件系统需要一个特殊的名字!
UserLAnd目前仅支持一个会话!
- UserLAnd目前无法支持您的设备.
除错日志已被删除或者不存在!
图像:箭头指示会话与文件系统之间的关系
图像:分发图标
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 5fb4ea398..0d7e1df4a 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -193,7 +193,6 @@
工作階段必須有獨一無二的名稱!
檔案系統必須有獨一無二的名稱!
UserLAnd 目前僅支援單一個作用中的工作階段!
- UserLAnd 目前並不支援您的裝置架構。
除錯紀錄檔已被刪除或不存在!
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 67295a159..2f32957f2 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -226,7 +226,6 @@
Sessions require a unique name!
Filesystems require a unique name!
UserLAnd currently only supports a single active session!
- UserLAnd does not currently support your devices architecture.
Debug log exported successfully!
Exporting debug log failed!
Debug log deleted!
diff --git a/app/src/test/java/tech/ula/model/remote/GithubApiClientTest.kt b/app/src/test/java/tech/ula/model/remote/GithubApiClientTest.kt
index 60566ec7d..518b243cc 100644
--- a/app/src/test/java/tech/ula/model/remote/GithubApiClientTest.kt
+++ b/app/src/test/java/tech/ula/model/remote/GithubApiClientTest.kt
@@ -16,7 +16,7 @@ import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnitRunner
import tech.ula.utils.Logger
-import tech.ula.utils.DeviceArchitecture
+import tech.ula.utils.UlaFiles
import java.io.IOException
@RunWith(MockitoJUnitRunner::class)
@@ -24,7 +24,7 @@ class GithubApiClientTest {
@get:Rule val server = MockWebServer()
- @Mock lateinit var mockDeviceArchitecture: DeviceArchitecture
+ @Mock lateinit var mockUlaFiles: UlaFiles
@Mock lateinit var mockUrlProvider: UrlProvider
@@ -76,9 +76,9 @@ class GithubApiClientTest {
@Before
fun setup() {
- whenever(mockDeviceArchitecture.getArchType()).thenReturn(testArch)
+ whenever(mockUlaFiles.getArchType()).thenReturn(testArch)
- githubApiClient = GithubApiClient(mockDeviceArchitecture, mockUrlProvider, mockLogger)
+ githubApiClient = GithubApiClient(mockUlaFiles, mockUrlProvider, mockLogger)
}
@After
diff --git a/app/src/test/java/tech/ula/model/state/AppsStartupFsmTest.kt b/app/src/test/java/tech/ula/model/state/AppsStartupFsmTest.kt
index 94f51cd00..b70ebacaa 100644
--- a/app/src/test/java/tech/ula/model/state/AppsStartupFsmTest.kt
+++ b/app/src/test/java/tech/ula/model/state/AppsStartupFsmTest.kt
@@ -42,7 +42,7 @@ class AppsStartupFsmTest {
@Mock lateinit var mockFilesystemManager: FilesystemManager
- @Mock lateinit var mockDeviceArchitecture: DeviceArchitecture
+ @Mock lateinit var mockUlaFiles: UlaFiles
@Mock lateinit var mockLogger: Logger
@@ -97,7 +97,7 @@ class AppsStartupFsmTest {
whenever(mockUlaDatabase.filesystemDao()).thenReturn(mockFilesystemDao)
whenever(mockUlaDatabase.sessionDao()).thenReturn(mockSessionDao)
- appsFsm = AppsStartupFsm(mockUlaDatabase, mockFilesystemManager, mockDeviceArchitecture, mockLogger)
+ appsFsm = AppsStartupFsm(mockUlaDatabase, mockFilesystemManager, mockUlaFiles, mockLogger)
}
@Test
@@ -165,7 +165,7 @@ class AppsStartupFsmTest {
whenever(mockSessionDao.findAppsSession(app.name))
.thenReturn(listOf(appSession))
- whenever(mockDeviceArchitecture.getArchType())
+ whenever(mockUlaFiles.getArchType())
.thenReturn("")
whenever(mockFilesystemDao.findAppsFilesystemByType(app.filesystemRequired))
.thenReturn(listOf())
@@ -219,7 +219,7 @@ class AppsStartupFsmTest {
appsFsm.setState(WaitingForAppSelection)
appsFsm.getState().observeForever(mockStateObserver)
- whenever(mockDeviceArchitecture.getArchType())
+ whenever(mockUlaFiles.getArchType())
.thenReturn("")
whenever(mockFilesystemDao.findAppsFilesystemByType(app.filesystemRequired))
.thenReturn(listOf())
diff --git a/app/src/test/java/tech/ula/utils/LocalServerManagerTest.kt b/app/src/test/java/tech/ula/utils/LocalServerManagerTest.kt
index e9858d2cb..8a1f03735 100644
--- a/app/src/test/java/tech/ula/utils/LocalServerManagerTest.kt
+++ b/app/src/test/java/tech/ula/utils/LocalServerManagerTest.kt
@@ -42,7 +42,7 @@ class LocalServerManagerTest {
private fun createVNCPidFile(session: Session) {
val folder = tempFolder.newFolder(filesystemDirName, "home", session.username, ".vnc")
- vncPidFile = File("${folder.path}/localhost:${session.port}.pid")
+ vncPidFile = File("${folder.path}/localhost:51.pid")
vncPidFile.createNewFile()
}