From 451d646775664ea8031a64eb4441e4795e20a943 Mon Sep 17 00:00:00 2001 From: Thomas Luong Date: Wed, 27 Feb 2019 10:16:32 -0800 Subject: [PATCH] Automatically set VNC dimensions to device dimensions (#657) * Add DeviceDimensionsUtility to save and get device dimensions * When starting VNC session, save device dimensions and set the session's `geometry` property * Pass the session's `geometry` as an environment variable * Add `geometry` field to Session to hold screen dimensions. Useful when starting VNC sessions * Lint * Fix unit tests * Rename saveDeviceDImensions to getDeviceDimensions and add unit test * Add missing import --- .../5.json | 259 ++++++++++++++++++ .../java/tech/ula/model/MigrationTest.kt | 13 +- app/src/main/java/tech/ula/MainActivity.kt | 27 +- .../java/tech/ula/model/entities/Session.kt | 1 + .../ula/model/repositories/UlaDatabase.kt | 10 +- .../java/tech/ula/utils/AndroidUtility.kt | 20 ++ .../main/java/tech/ula/utils/ServerUtility.kt | 1 + .../tech/ula/utils/DeviceDimensionsTest.kt | 55 ++++ .../java/tech/ula/utils/ServerUtilityTest.kt | 2 +- 9 files changed, 377 insertions(+), 11 deletions(-) create mode 100644 app/schemas/tech.ula.model.repositories.UlaDatabase/5.json create mode 100644 app/src/test/java/tech/ula/utils/DeviceDimensionsTest.kt diff --git a/app/schemas/tech.ula.model.repositories.UlaDatabase/5.json b/app/schemas/tech.ula.model.repositories.UlaDatabase/5.json new file mode 100644 index 000000000..88e452c40 --- /dev/null +++ b/app/schemas/tech.ula.model.repositories.UlaDatabase/5.json @@ -0,0 +1,259 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "e0ab57d3b8ac504ff016fa68d9ee5478", + "entities": [ + { + "tableName": "session", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `filesystemId` INTEGER NOT NULL, `filesystemName` TEXT NOT NULL, `active` INTEGER NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, `vncPassword` TEXT NOT NULL, `serviceType` TEXT NOT NULL, `port` INTEGER NOT NULL, `pid` INTEGER NOT NULL, `geometry` TEXT NOT NULL, `isAppsSession` INTEGER NOT NULL, FOREIGN KEY(`filesystemId`) REFERENCES `filesystem`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filesystemId", + "columnName": "filesystemId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "filesystemName", + "columnName": "filesystemName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "active", + "columnName": "active", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "vncPassword", + "columnName": "vncPassword", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "serviceType", + "columnName": "serviceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "port", + "columnName": "port", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "pid", + "columnName": "pid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "geometry", + "columnName": "geometry", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isAppsSession", + "columnName": "isAppsSession", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_session_filesystemId", + "unique": false, + "columnNames": [ + "filesystemId" + ], + "createSql": "CREATE INDEX `index_session_filesystemId` ON `${TABLE_NAME}` (`filesystemId`)" + } + ], + "foreignKeys": [ + { + "table": "filesystem", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "filesystemId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "filesystem", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `distributionType` TEXT NOT NULL, `archType` TEXT NOT NULL, `defaultUsername` TEXT NOT NULL, `defaultPassword` TEXT NOT NULL, `defaultVncPassword` TEXT NOT NULL, `isAppsFilesystem` INTEGER NOT NULL, `lastUpdated` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "distributionType", + "columnName": "distributionType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "archType", + "columnName": "archType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "defaultUsername", + "columnName": "defaultUsername", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "defaultPassword", + "columnName": "defaultPassword", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "defaultVncPassword", + "columnName": "defaultVncPassword", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isAppsFilesystem", + "columnName": "isAppsFilesystem", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdated", + "columnName": "lastUpdated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "apps", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `category` TEXT NOT NULL, `filesystemRequired` TEXT NOT NULL, `supportsCli` INTEGER NOT NULL, `supportsGui` INTEGER NOT NULL, `isPaidApp` INTEGER NOT NULL, `version` INTEGER NOT NULL, PRIMARY KEY(`name`))", + "fields": [ + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filesystemRequired", + "columnName": "filesystemRequired", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "supportsCli", + "columnName": "supportsCli", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "supportsGui", + "columnName": "supportsGui", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isPaidApp", + "columnName": "isPaidApp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "name" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_apps_name", + "unique": true, + "columnNames": [ + "name" + ], + "createSql": "CREATE UNIQUE INDEX `index_apps_name` ON `${TABLE_NAME}` (`name`)" + } + ], + "foreignKeys": [] + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"e0ab57d3b8ac504ff016fa68d9ee5478\")" + ] + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/tech/ula/model/MigrationTest.kt b/app/src/androidTest/java/tech/ula/model/MigrationTest.kt index fe82aa41a..830cc4f0e 100644 --- a/app/src/androidTest/java/tech/ula/model/MigrationTest.kt +++ b/app/src/androidTest/java/tech/ula/model/MigrationTest.kt @@ -18,6 +18,7 @@ import tech.ula.model.entities.Session import tech.ula.model.repositories.Migration1To2 import tech.ula.model.repositories.Migration2To3 import tech.ula.model.repositories.Migration3To4 +import tech.ula.model.repositories.Migration4To5 import tech.ula.model.repositories.UlaDatabase import java.io.IOException @@ -42,7 +43,7 @@ class MigrationTest { db.close() - helper.runMigrationsAndValidate(TEST_DB, 2, true, Migration1To2(), Migration2To3(), Migration3To4()) + helper.runMigrationsAndValidate(TEST_DB, 2, true, Migration1To2(), Migration2To3(), Migration3To4(), Migration4To5()) } @Test @@ -98,10 +99,18 @@ class MigrationTest { helper.runMigrationsAndValidate(TEST_DB, 4, true, Migration3To4()) } + @Test + @Throws(IOException::class) + fun migrate4To5() { + helper.createDatabase(TEST_DB, 4) + + helper.runMigrationsAndValidate(TEST_DB, 5, true, Migration4To5()) + } + private fun getMigratedDatabase(): UlaDatabase { val db = Room.databaseBuilder(InstrumentationRegistry.getTargetContext(), UlaDatabase::class.java, TEST_DB) - .addMigrations(Migration1To2(), Migration2To3(), Migration3To4()) + .addMigrations(Migration1To2(), Migration2To3(), Migration3To4(), Migration4To5()) .build() helper.closeWhenFinished(db) diff --git a/app/src/main/java/tech/ula/MainActivity.kt b/app/src/main/java/tech/ula/MainActivity.kt index cf4065220..654b6afdc 100644 --- a/app/src/main/java/tech/ula/MainActivity.kt +++ b/app/src/main/java/tech/ula/MainActivity.kt @@ -22,10 +22,12 @@ import android.os.Environment import android.support.design.widget.TextInputEditText import android.support.v4.content.LocalBroadcastManager import android.support.v7.app.AppCompatActivity +import android.util.DisplayMetrics import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.view.WindowManager import android.view.animation.AlphaAnimation import android.widget.Button import android.widget.RadioButton @@ -316,13 +318,26 @@ class MainActivity : AppCompatActivity(), SessionListFragment.SessionSelection, // TODO: Alert user when defaulting to VNC if (session.serviceType == "xsdl" && Build.VERSION.SDK_INT > Build.VERSION_CODES.O_MR1) { session.serviceType = "vnc" - startSession(session) - } else if (session.serviceType == "xsdl") { - viewModel.lastSelectedSession = session - sendXsdlIntentToSetDisplayNumberAndExpectResult() - } else { - startSession(session) } + + when (session.serviceType) { + "xsdl" -> { + viewModel.lastSelectedSession = session + sendXsdlIntentToSetDisplayNumberAndExpectResult() + } + "vnc" -> { + getDeviceDimensions(session) + startSession(session) + } + else -> startSession(session) + } + } + + private fun getDeviceDimensions(session: Session) { + val deviceDimensions = DeviceDimensions() + val windowManager = applicationContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager + deviceDimensions.getDeviceDimensions(windowManager, DisplayMetrics()) + session.geometry = deviceDimensions.getGeometry() } private fun startSession(session: Session) { 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 c1eea14e9..b12033b3b 100644 --- a/app/src/main/java/tech/ula/model/entities/Session.kt +++ b/app/src/main/java/tech/ula/model/entities/Session.kt @@ -30,6 +30,7 @@ data class Session( var serviceType: String = "", var port: Long = 2022, var pid: Long = 0, + var geometry: String = "", val isAppsSession: Boolean = false ) : Parcelable { override fun toString(): String { diff --git a/app/src/main/java/tech/ula/model/repositories/UlaDatabase.kt b/app/src/main/java/tech/ula/model/repositories/UlaDatabase.kt index 0d864b575..923b2f7f4 100644 --- a/app/src/main/java/tech/ula/model/repositories/UlaDatabase.kt +++ b/app/src/main/java/tech/ula/model/repositories/UlaDatabase.kt @@ -15,7 +15,7 @@ import tech.ula.model.daos.FilesystemDao import tech.ula.model.daos.SessionDao import tech.ula.model.entities.App -@Database(entities = [Session::class, Filesystem::class, App::class], version = 4, exportSchema = true) +@Database(entities = [Session::class, Filesystem::class, App::class], version = 5, exportSchema = true) abstract class UlaDatabase : RoomDatabase() { abstract fun sessionDao(): SessionDao @@ -36,7 +36,7 @@ abstract class UlaDatabase : RoomDatabase() { private fun buildDatabase(context: Context): UlaDatabase = Room.databaseBuilder(context.applicationContext, UlaDatabase::class.java, "Data.db") - .addMigrations(Migration1To2(), Migration2To3(), Migration3To4()) + .addMigrations(Migration1To2(), Migration2To3(), Migration3To4(), Migration4To5()) .addCallback(object : RoomDatabase.Callback() { override fun onOpen(db: SupportSQLiteDatabase) { super.onOpen(db) @@ -110,4 +110,10 @@ class Migration3To4 : Migration(3, 4) { database.execSQL("COMMIT;") database.execSQL("PRAGMA foreign_keys_on") } +} + +class Migration4To5 : Migration(4, 5) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE session ADD COLUMN geometry TEXT NOT NULL DEFAULT ''") + } } \ No newline at end of file diff --git a/app/src/main/java/tech/ula/utils/AndroidUtility.kt b/app/src/main/java/tech/ula/utils/AndroidUtility.kt index 612ba9cf0..a16c2ecbe 100644 --- a/app/src/main/java/tech/ula/utils/AndroidUtility.kt +++ b/app/src/main/java/tech/ula/utils/AndroidUtility.kt @@ -14,6 +14,8 @@ import android.net.Uri import android.os.Build import android.os.Environment import android.support.v4.content.ContextCompat +import android.util.DisplayMetrics +import android.view.WindowManager import org.acra.ACRA import tech.ula.R import tech.ula.model.entities.App @@ -398,6 +400,24 @@ class AcraWrapper { } } +class DeviceDimensions { + private var width = 720 + private var height = 1480 + + fun getDeviceDimensions(windowManager: WindowManager, displayMetrics: DisplayMetrics) { + windowManager.defaultDisplay.getMetrics(displayMetrics) + width = displayMetrics.widthPixels + height = displayMetrics.heightPixels + } + + fun getGeometry(): String { + return when (height > width) { + true -> "${height}x$width" + false -> "${width}x$height" + } + } +} + class UserFeedbackUtility(private val prefs: SharedPreferences) { private val numberOfTimesOpenedKey = "numberOfTimesOpened" private val userGaveFeedbackKey = "userGaveFeedback" diff --git a/app/src/main/java/tech/ula/utils/ServerUtility.kt b/app/src/main/java/tech/ula/utils/ServerUtility.kt index 23aad265c..a6eafe397 100644 --- a/app/src/main/java/tech/ula/utils/ServerUtility.kt +++ b/app/src/main/java/tech/ula/utils/ServerUtility.kt @@ -71,6 +71,7 @@ class ServerUtility( val env = HashMap() env["INITIAL_USERNAME"] = session.username env["INITIAL_VNC_PASSWORD"] = session.vncPassword + env["DIMENSIONS"] = session.geometry val process = execUtility.wrapWithBusyboxAndExecute(targetDirectoryName, command, doWait = false, environmentVars = env) process.pid() } catch (err: Exception) { diff --git a/app/src/test/java/tech/ula/utils/DeviceDimensionsTest.kt b/app/src/test/java/tech/ula/utils/DeviceDimensionsTest.kt new file mode 100644 index 000000000..32e23d2ce --- /dev/null +++ b/app/src/test/java/tech/ula/utils/DeviceDimensionsTest.kt @@ -0,0 +1,55 @@ +package tech.ula.utils + +import android.util.DisplayMetrics +import android.view.Display +import android.view.WindowManager +import com.nhaarman.mockitokotlin2.whenever +import junit.framework.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnitRunner + +@RunWith(MockitoJUnitRunner::class) +class DeviceDimensionsTest { + + @Mock + lateinit var deviceDimensions: DeviceDimensions + + @Mock + lateinit var windowManager: WindowManager + + @Mock + lateinit var displayMetrics: DisplayMetrics + + @Mock + lateinit var defaultDisplay: Display + + @Before + fun setup() { + deviceDimensions = DeviceDimensions() + whenever(windowManager.defaultDisplay).thenReturn(defaultDisplay) + } + + private fun setDimensions(displayMetrics: DisplayMetrics, width: Int, height: Int) { + displayMetrics.widthPixels = width + displayMetrics.heightPixels = height + } + + @Test + fun `Device dimensions that are taller in height will have the height value first`() { + setDimensions(displayMetrics, width = 100, height = 200) + deviceDimensions.getDeviceDimensions(windowManager, displayMetrics) + val geometry = deviceDimensions.getGeometry() + assertEquals(geometry, "200x100") + } + + @Test + fun `Device dimensions that are longer in width will have the width value first`() { + setDimensions(displayMetrics, width = 300, height = 200) + deviceDimensions.getDeviceDimensions(windowManager, displayMetrics) + val geometry = deviceDimensions.getGeometry() + assertEquals(geometry, "300x200") + } +} \ No newline at end of file diff --git a/app/src/test/java/tech/ula/utils/ServerUtilityTest.kt b/app/src/test/java/tech/ula/utils/ServerUtilityTest.kt index 2a50d59c8..2863e2f85 100644 --- a/app/src/test/java/tech/ula/utils/ServerUtilityTest.kt +++ b/app/src/test/java/tech/ula/utils/ServerUtilityTest.kt @@ -69,7 +69,7 @@ class ServerUtilityTest { fun startVNCServer() { val session = Session(0, filesystemId = 0, serviceType = "vnc", username = "user", vncPassword = "userland") val command = "../support/execInProot.sh /bin/bash -c /support/startVNCServer.sh" - val env = hashMapOf("INITIAL_USERNAME" to "user", "INITIAL_VNC_PASSWORD" to "userland") + val env = hashMapOf("INITIAL_USERNAME" to "user", "INITIAL_VNC_PASSWORD" to "userland", "DIMENSIONS" to "") `when`(execUtility.wrapWithBusyboxAndExecute("0", command, doWait = false, environmentVars = env)).thenReturn(process)