serialize to json

This commit is contained in:
Josia Pietsch 2024-12-23 02:29:17 +01:00
parent 36ee8033ed
commit 970c160f4a
Signed by: jrpie
GPG key ID: E70B571D66986A2D
12 changed files with 668 additions and 572 deletions

View file

@ -1,9 +1,9 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'kotlinx-serialization'
android { android {
dataBinding { dataBinding {
enabled = true enabled = true
} }
@ -76,6 +76,7 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:1.3.2' implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.preference:preference-ktx:1.2.1' implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'com.google.android.material:material:1.12.0' implementation 'com.google.android.material:material:1.12.0'
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
implementation "eu.jonahbauer:android-preference-annotations:1.1.2" implementation "eu.jonahbauer:android-preference-annotations:1.1.2"
annotationProcessor "eu.jonahbauer:android-preference-annotations:1.1.2" annotationProcessor "eu.jonahbauer:android-preference-annotations:1.1.2"
annotationProcessor "com.android.databinding:compiler:$android_plugin_version" annotationProcessor "com.android.databinding:compiler:$android_plugin_version"

View file

@ -8,61 +8,36 @@ import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.widget.Toast import android.widget.Toast
import de.jrpie.android.launcher.R import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.apps.AppInfo
import de.jrpie.android.launcher.apps.AppInfo.Companion.INVALID_USER
import de.jrpie.android.launcher.apps.DetailedAppInfo
import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.LauncherPreferences
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
interface Action {
@Serializable
sealed interface Action {
fun invoke(context: Context, rect: Rect? = null): Boolean fun invoke(context: Context, rect: Rect? = null): Boolean
fun bindToGesture(prefEditor: Editor, id: String)
fun label(context: Context): String fun label(context: Context): String
fun getIcon(context: Context): Drawable? fun getIcon(context: Context): Drawable?
fun isAvailable(context: Context): Boolean fun isAvailable(context: Context): Boolean
fun writeToIntent(intent: Intent)
fun bindToGesture(prefEditor: Editor, id: String) {
prefEditor.putString(id, Json.encodeToString(this))
}
fun writeToIntent(intent: Intent) {
intent.putExtra("action", Json.encodeToString(this))
}
companion object { companion object {
/**
* Get an action for a specific id.
* An id is of the form:
* - "launcher:${launcher_action_name}", see [LauncherAction]
* - "${package_name}", see [AppAction]
* - "${package_name}:${activity_name}", see [AppAction]
*
* @param id
* @param user a user id, ignored if the action is a [LauncherAction].
* @param context used to complete [AppInfo] if possible
*/
private fun fromId(id: String, user: Int?, context: Context? = null): Action? {
if (id.isEmpty()) {
return null
}
if (LauncherAction.isOtherAction(id)) {
return LauncherAction.byId(id)
}
val values = id.split(";")
var info = AppInfo(values[0], values.getOrNull(1), user ?: INVALID_USER)
// try to complete an incomplete AppInfo if a context is provided
if (context != null && (info.user == INVALID_USER || info.activityName == null)) {
info = DetailedAppInfo.fromAppInfo(info, context)?.app?:info
}
return AppAction(info)
}
fun forGesture(gesture: Gesture): Action? { fun forGesture(gesture: Gesture): Action? {
val id = gesture.id val id = gesture.id
val preferences = LauncherPreferences.getSharedPreferences() val preferences = LauncherPreferences.getSharedPreferences()
val actionId = preferences.getString("$id.app", "")!! val json = preferences.getString(id, "null")!!
var u: Int? = preferences.getInt("$id.user", INVALID_USER) return Json.decodeFromString(json)
u = if (u == INVALID_USER) null else u
return fromId(actionId, u)
} }
fun resetToDefaultActions(context: Context) { fun resetToDefaultActions(context: Context) {
@ -72,11 +47,11 @@ interface Action {
context.resources context.resources
.getStringArray(gesture.defaultsResource) .getStringArray(gesture.defaultsResource)
.filterNot { boundActions.contains(it) } .filterNot { boundActions.contains(it) }
.map { Pair(it, fromId(it, null, context)) } .map { Pair(it, Json.decodeFromString<Action>(it)) }
.firstOrNull { it.second?.isAvailable(context) ?: false } .firstOrNull { it.second.isAvailable(context) }
?.apply { ?.apply {
boundActions.add(first) boundActions.add(first)
second?.bindToGesture(editor, gesture.id) second.bindToGesture(editor, gesture.id)
} }
} }
editor.apply() editor.apply()
@ -94,8 +69,7 @@ interface Action {
fun clearActionForGesture(gesture: Gesture) { fun clearActionForGesture(gesture: Gesture) {
LauncherPreferences.getSharedPreferences().edit() LauncherPreferences.getSharedPreferences().edit()
.putString(gesture.id + ".app", "") .remove(gesture.id)
.putInt(gesture.id + ".user", INVALID_USER)
.apply() .apply()
} }
@ -119,9 +93,8 @@ interface Action {
} }
fun fromIntent(data: Intent): Action? { fun fromIntent(data: Intent): Action? {
val value = data.getStringExtra("action_id") ?: return null val json = data.getStringExtra("action") ?: return null
val user = data.getIntExtra("user", INVALID_USER) return Json.decodeFromString(json)
return fromId(value, user)
} }
} }
} }

View file

@ -4,7 +4,6 @@ import android.app.AlertDialog
import android.app.Service import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.LauncherApps import android.content.pm.LauncherApps
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
@ -14,16 +13,20 @@ import de.jrpie.android.launcher.apps.AppInfo
import de.jrpie.android.launcher.apps.AppInfo.Companion.INVALID_USER import de.jrpie.android.launcher.apps.AppInfo.Companion.INVALID_USER
import de.jrpie.android.launcher.apps.DetailedAppInfo import de.jrpie.android.launcher.apps.DetailedAppInfo
import de.jrpie.android.launcher.ui.list.apps.openSettings import de.jrpie.android.launcher.ui.list.apps.openSettings
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
class AppAction(val appInfo: AppInfo) : Action { @Serializable
@SerialName("action:app")
class AppAction(val app: AppInfo) : Action {
override fun invoke(context: Context, rect: Rect?): Boolean { override fun invoke(context: Context, rect: Rect?): Boolean {
val packageName = appInfo.packageName.toString() val packageName = app.packageName
if (appInfo.user != INVALID_USER) { if (app.user != INVALID_USER) {
val launcherApps = val launcherApps =
context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps
appInfo.getLauncherActivityInfo(context)?.let { app -> app.getLauncherActivityInfo(context)?.let { app ->
Log.i("Launcher", "Starting $appInfo") Log.i("Launcher", "Starting ${this.app}")
launcherApps.startMainActivity(app.componentName, app.user, rect, null) launcherApps.startMainActivity(app.componentName, app.user, rect, null)
return true return true
} }
@ -44,7 +47,7 @@ class AppAction(val appInfo: AppInfo) : Action {
.setTitle(context.getString(R.string.alert_cant_open_title)) .setTitle(context.getString(R.string.alert_cant_open_title))
.setMessage(context.getString(R.string.alert_cant_open_message)) .setMessage(context.getString(R.string.alert_cant_open_message))
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
appInfo.openSettings(context) app.openSettings(context)
} }
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setIcon(android.R.drawable.ic_dialog_info) .setIcon(android.R.drawable.ic_dialog_info)
@ -55,33 +58,15 @@ class AppAction(val appInfo: AppInfo) : Action {
} }
override fun label(context: Context): String { override fun label(context: Context): String {
return DetailedAppInfo.fromAppInfo(appInfo, context)?.getCustomLabel(context).toString() return DetailedAppInfo.fromAppInfo(app, context)?.getCustomLabel(context).toString()
} }
override fun getIcon(context: Context): Drawable? { override fun getIcon(context: Context): Drawable? {
return DetailedAppInfo.fromAppInfo(appInfo, context)?.icon return DetailedAppInfo.fromAppInfo(app, context)?.icon
} }
override fun isAvailable(context: Context): Boolean { override fun isAvailable(context: Context): Boolean {
// check if app is installed // check if app is installed
return DetailedAppInfo.fromAppInfo(appInfo, context) != null return DetailedAppInfo.fromAppInfo(app, context) != null
}
override fun bindToGesture(editor: SharedPreferences.Editor, id: String) {
val u = appInfo.user
// TODO: replace this by AppInfo#serialize (breaking change to SharedPreferences!)
var app = appInfo.packageName.toString()
if (appInfo.activityName != null) {
app += ";${appInfo.activityName}"
}
editor
.putString("$id.app", app)
.putInt("$id.user", u)
}
override fun writeToIntent(intent: Intent) {
intent.putExtra("action_id", "${appInfo.packageName};${appInfo.activityName}")
intent.putExtra("user", appInfo.user)
} }
} }

View file

@ -2,7 +2,6 @@ package de.jrpie.android.launcher.actions
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences.Editor
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.media.AudioManager import android.media.AudioManager
@ -13,11 +12,20 @@ import android.widget.Toast
import de.jrpie.android.launcher.Application import de.jrpie.android.launcher.Application
import de.jrpie.android.launcher.R import de.jrpie.android.launcher.R
import de.jrpie.android.launcher.apps.AppFilter import de.jrpie.android.launcher.apps.AppFilter
import de.jrpie.android.launcher.apps.AppInfo.Companion.INVALID_USER
import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.ui.list.ListActivity import de.jrpie.android.launcher.ui.list.ListActivity
import de.jrpie.android.launcher.ui.settings.SettingsActivity import de.jrpie.android.launcher.ui.settings.SettingsActivity
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
@Serializable(with = LauncherActionSerializer::class)
@SerialName("action:launcher")
enum class LauncherAction( enum class LauncherAction(
val id: String, val id: String,
val label: Int, val label: Int,
@ -25,68 +33,68 @@ enum class LauncherAction(
val launch: (Context) -> Unit val launch: (Context) -> Unit
) : Action { ) : Action {
SETTINGS( SETTINGS(
"launcher:settings", "settings",
R.string.list_other_settings, R.string.list_other_settings,
R.drawable.baseline_settings_24, R.drawable.baseline_settings_24,
::openSettings ::openSettings
), ),
CHOOSE( CHOOSE(
"launcher:choose", "choose",
R.string.list_other_list, R.string.list_other_list,
R.drawable.baseline_menu_24, R.drawable.baseline_menu_24,
::openAppsList ::openAppsList
), ),
CHOOSE_FROM_FAVORITES( CHOOSE_FROM_FAVORITES(
"launcher:chooseFromFavorites", "choose_from_favorites",
R.string.list_other_list_favorites, R.string.list_other_list_favorites,
R.drawable.baseline_favorite_24, R.drawable.baseline_favorite_24,
{ context -> openAppsList(context, true) } { context -> openAppsList(context, true) }
), ),
VOLUME_UP( VOLUME_UP(
"launcher:volumeUp", "volume_up",
R.string.list_other_volume_up, R.string.list_other_volume_up,
R.drawable.baseline_volume_up_24, ::audioVolumeUp R.drawable.baseline_volume_up_24, ::audioVolumeUp
), ),
VOLUME_DOWN( VOLUME_DOWN(
"launcher:volumeDown", "volume_down",
R.string.list_other_volume_down, R.string.list_other_volume_down,
R.drawable.baseline_volume_down_24, ::audioVolumeDown R.drawable.baseline_volume_down_24, ::audioVolumeDown
), ),
TRACK_NEXT( TRACK_NEXT(
"launcher:nextTrack", "next_track",
R.string.list_other_track_next, R.string.list_other_track_next,
R.drawable.baseline_skip_next_24, ::audioNextTrack R.drawable.baseline_skip_next_24, ::audioNextTrack
), ),
TRACK_PREV( TRACK_PREV(
"launcher:previousTrack", "previous_track",
R.string.list_other_track_previous, R.string.list_other_track_previous,
R.drawable.baseline_skip_previous_24, ::audioPreviousTrack R.drawable.baseline_skip_previous_24, ::audioPreviousTrack
), ),
EXPAND_NOTIFICATIONS_PANEL( EXPAND_NOTIFICATIONS_PANEL(
"launcher:expandNotificationsPanel", "expand_notifications_panel",
R.string.list_other_expand_notifications_panel, R.string.list_other_expand_notifications_panel,
R.drawable.baseline_notifications_24, R.drawable.baseline_notifications_24,
::expandNotificationsPanel ::expandNotificationsPanel
), ),
EXPAND_SETTINGS_PANEL( EXPAND_SETTINGS_PANEL(
"launcher:expandSettingsPanel", "expand_settings_panel",
R.string.list_other_expand_settings_panel, R.string.list_other_expand_settings_panel,
R.drawable.baseline_settings_applications_24, R.drawable.baseline_settings_applications_24,
::expandSettingsPanel ::expandSettingsPanel
), ),
LOCK_SCREEN( LOCK_SCREEN(
"launcher:lockScreen", "lock_screen",
R.string.list_other_lock_screen, R.string.list_other_lock_screen,
R.drawable.baseline_lock_24px, R.drawable.baseline_lock_24px,
{ c -> LauncherPreferences.actions().lockMethod().lockOrEnable(c) } { c -> LauncherPreferences.actions().lockMethod().lockOrEnable(c) }
), ),
TORCH( TORCH(
"launcher:toggleTorch", "toggle_torch",
R.string.list_other_torch, R.string.list_other_torch,
R.drawable.baseline_flashlight_on_24, R.drawable.baseline_flashlight_on_24,
::toggleTorch ::toggleTorch
), ),
NOP("launcher:nop", R.string.list_other_nop, R.drawable.baseline_not_interested_24, {}); NOP("nop", R.string.list_other_nop, R.drawable.baseline_not_interested_24, {});
override fun invoke(context: Context, rect: Rect?): Boolean { override fun invoke(context: Context, rect: Rect?): Boolean {
launch(context) launch(context)
@ -101,16 +109,6 @@ enum class LauncherAction(
return context.getDrawable(icon) return context.getDrawable(icon)
} }
override fun bindToGesture(editor: Editor, id: String) {
editor
.putString("$id.app", this.id)
.putInt("$id.user", INVALID_USER)
}
override fun writeToIntent(intent: Intent) {
intent.putExtra("action_id", id)
}
override fun isAvailable(context: Context): Boolean { override fun isAvailable(context: Context): Boolean {
return true return true
} }
@ -119,10 +117,6 @@ enum class LauncherAction(
fun byId(id: String): LauncherAction? { fun byId(id: String): LauncherAction? {
return entries.singleOrNull { it.id == id } return entries.singleOrNull { it.id == id }
} }
fun isOtherAction(id: String): Boolean {
return id.startsWith("launcher")
}
} }
} }
@ -253,3 +247,31 @@ fun openAppsList(context: Context, favorite: Boolean = false, hidden: Boolean =
context.startActivity(intent) context.startActivity(intent)
} }
/**
* LauncherAction can't be serialized directly, since it needs a type annotation.
* Thus this hack is needed.
*/
@Serializable
private class LauncherActionWrapper(val id: String)
private class LauncherActionSerializer() : KSerializer<LauncherAction> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor(
"action:launcher",
) {
element("id", LauncherActionWrapper.serializer().descriptor)
}
override fun deserialize(decoder: Decoder): LauncherAction {
val wrapper = decoder.decodeSerializableValue(LauncherActionWrapper.serializer())
return LauncherAction.byId(wrapper.id) ?: throw SerializationException()
}
override fun serialize(encoder: Encoder, value: LauncherAction) {
encoder.encodeSerializableValue(
LauncherActionWrapper.serializer(),
LauncherActionWrapper(value.id)
)
}
}

View file

@ -29,7 +29,7 @@ class AppFilter(
if (LauncherPreferences.apps().hideBoundApps()) { if (LauncherPreferences.apps().hideBoundApps()) {
val boundApps = Gesture.entries val boundApps = Gesture.entries
.filter(Gesture::isEnabled) .filter(Gesture::isEnabled)
.mapNotNull { g -> (Action.forGesture(g) as? AppAction)?.appInfo } .mapNotNull { g -> (Action.forGesture(g) as? AppAction)?.app }
.toSet() .toSet()
apps = apps.filterNot { info -> boundApps.contains(info.app) } apps = apps.filterNot { info -> boundApps.contains(info.app) }
} }

View file

@ -5,21 +5,19 @@ import android.content.Context
import android.content.pm.LauncherActivityInfo import android.content.pm.LauncherActivityInfo
import android.content.pm.LauncherApps import android.content.pm.LauncherApps
import de.jrpie.android.launcher.getUserFromId import de.jrpie.android.launcher.getUserFromId
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
/** /**
* Represents an app installed on the users device. * Represents an app installed on the users device.
* Contains the minimal amount of data required to identify the app. * Contains the minimal amount of data required to identify the app.
*/ */
class AppInfo(val packageName: CharSequence, val activityName: CharSequence?, val user: Int = INVALID_USER) { @Serializable
class AppInfo(val packageName: String, val activityName: String?, val user: Int = INVALID_USER) {
// TODO: make activityName non nullable (breaking change to SharedPreferences!)
fun serialize(): String { fun serialize(): String {
val u = user return Json.encodeToString(this)
var ret = "$packageName;$u"
activityName?.let { ret += ";$activityName" }
return ret
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@ -39,7 +37,7 @@ class AppInfo(val packageName: CharSequence, val activityName: CharSequence?, va
): LauncherActivityInfo? { ): LauncherActivityInfo? {
val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps
val userHandle = getUserFromId(user, context) val userHandle = getUserFromId(user, context)
val activityList = launcherApps.getActivityList(packageName.toString(), userHandle) val activityList = launcherApps.getActivityList(packageName, userHandle)
return activityList.firstOrNull { app -> app.name == activityName } return activityList.firstOrNull { app -> app.name == activityName }
?: activityList.firstOrNull() ?: activityList.firstOrNull()
} }
@ -53,11 +51,7 @@ class AppInfo(val packageName: CharSequence, val activityName: CharSequence?, va
const val INVALID_USER = -1 const val INVALID_USER = -1
fun deserialize(serialized: String): AppInfo { fun deserialize(serialized: String): AppInfo {
val values = serialized.split(";") return Json.decodeFromString(serialized)
val packageName = values[0]
val user = Integer.valueOf(values[1])
val activityName = values.getOrNull(2)
return AppInfo(packageName, activityName, user)
} }
} }
} }

View file

@ -107,6 +107,7 @@ public final class LauncherPreferences$Config {
} }
} }
// TODO migrate to version 2
public static class MapAppInfoStringSerializer implements PreferenceSerializer<HashMap<AppInfo, String>, Set<String>> { public static class MapAppInfoStringSerializer implements PreferenceSerializer<HashMap<AppInfo, String>, Set<String>> {
@Override @Override

View file

@ -1,57 +1,24 @@
package de.jrpie.android.launcher.preferences package de.jrpie.android.launcher.preferences
import android.content.Context import android.content.Context
import android.content.SharedPreferences
import android.util.Log import android.util.Log
import de.jrpie.android.launcher.BuildConfig import de.jrpie.android.launcher.BuildConfig
import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Action
import de.jrpie.android.launcher.apps.AppInfo import de.jrpie.android.launcher.apps.AppInfo
import de.jrpie.android.launcher.apps.DetailedAppInfo import de.jrpie.android.launcher.apps.DetailedAppInfo
import de.jrpie.android.launcher.preferences.theme.Background import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown
import de.jrpie.android.launcher.preferences.theme.ColorTheme import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion1
import de.jrpie.android.launcher.ui.HomeActivity import de.jrpie.android.launcher.ui.HomeActivity
/* Current version of the structure of preferences. /* Current version of the structure of preferences.
* Increase when breaking changes are introduced and write an appropriate case in * Increase when breaking changes are introduced and write an appropriate case in
* `migratePreferencesToNewVersion` * `migratePreferencesToNewVersion`
*/ */
const val PREFERENCE_VERSION = 1 const val PREFERENCE_VERSION = 2
const val UNKNOWN_PREFERENCE_VERSION = -1 const val UNKNOWN_PREFERENCE_VERSION = -1
private const val TAG = "Launcher - Preferences" private const val TAG = "Launcher - Preferences"
private fun migrateStringPreference(
oldPrefs: SharedPreferences,
newPreferences: SharedPreferences.Editor,
oldKey: String,
newKey: String,
default: String
) {
val s = oldPrefs.getString(oldKey, default)
newPreferences.putString(newKey, s)
}
private fun migrateIntPreference(
oldPrefs: SharedPreferences,
newPreferences: SharedPreferences.Editor,
oldKey: String,
newKey: String,
default: Int
) {
val s = oldPrefs.getInt(oldKey, default)
newPreferences.putInt(newKey, s)
}
private fun migrateBooleanPreference(
oldPrefs: SharedPreferences,
newPreferences: SharedPreferences.Editor,
oldKey: String,
newKey: String,
default: Boolean
) {
val s = oldPrefs.getBoolean(oldKey, default)
newPreferences.putBoolean(newKey, s)
}
fun migratePreferencesToNewVersion(context: Context) { fun migratePreferencesToNewVersion(context: Context) {
when (LauncherPreferences.internal().versionCode()) { when (LauncherPreferences.internal().versionCode()) {
@ -60,356 +27,23 @@ fun migratePreferencesToNewVersion(context: Context) {
} }
UNKNOWN_PREFERENCE_VERSION -> { /* still using the old preferences file */ UNKNOWN_PREFERENCE_VERSION -> { /* still using the old preferences file */
Log.i( migratePreferencesFromVersionUnknown(context)
TAG,
"Unknown preference version, trying to restore preferences from old version."
)
val oldPrefs = context.getSharedPreferences(
"V3RYR4ND0MK3YCR4P",
Context.MODE_PRIVATE
)
if (!oldPrefs.contains("startedBefore")) {
Log.i(TAG, "No old preferences found.")
return
}
val newPrefs = LauncherPreferences.getSharedPreferences().edit()
migrateBooleanPreference(
oldPrefs,
newPrefs,
"startedBefore",
"internal.started_before",
false
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_volumeUpApp",
"action.volume_up.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_volumeUpApp_user",
"action.volume_up.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_volumeDownApp",
"action.volume_down.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_volumeDownApp_user",
"action.volume_down.user",
-1
)
migrateStringPreference(oldPrefs, newPrefs, "action_timeApp", "action.time.app", "")
migrateIntPreference(oldPrefs, newPrefs, "action_timeApp_user", "action.time.user", -1)
migrateStringPreference(oldPrefs, newPrefs, "action_dateApp", "action.date.app", "")
migrateIntPreference(oldPrefs, newPrefs, "action_dateApp_user", "action.date.user", -1)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_longClickApp",
"action.long_click.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_longClickApp_user",
"action.long_click.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_doubleClickApp",
"action.double_click.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_doubleClickApp_user",
"action.double_click.user",
-1
)
migrateStringPreference(oldPrefs, newPrefs, "action_upApp", "action.up.app", "")
migrateIntPreference(oldPrefs, newPrefs, "action_upApp_user", "action.up.user", -1)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_up_leftApp",
"action.up_left.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_up_leftApp_user",
"action.up_left.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_up_rightApp",
"action.up_right.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_up_rightApp_user",
"action.up_right.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_doubleUpApp",
"action.double_up.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_doubleUpApp_user",
"action.double_up.user",
-1
)
migrateStringPreference(oldPrefs, newPrefs, "action_downApp", "action.down.app", "")
migrateIntPreference(oldPrefs, newPrefs, "action_downApp_user", "action.down.user", -1)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_down_leftApp",
"action.down_left.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_down_leftApp_user",
"action.down_left.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_down_rightApp",
"action.down_right.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_down_rightApp_user",
"action.down_right.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_doubleDownApp",
"action.double_down.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_doubleDownApp_user",
"action.double_down.user",
-1
)
migrateStringPreference(oldPrefs, newPrefs, "action_leftApp", "action.left.app", "")
migrateIntPreference(oldPrefs, newPrefs, "action_leftApp_user", "action.left.user", -1)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_left_topApp",
"action.left_top.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_left_topApp_user",
"action.left_top.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_left_bottomApp",
"action.left_bottom.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_left_bottomApp_user",
"action.left_bottom.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_doubleLeftApp",
"action.double_left.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_doubleLeftApp_user",
"action.double_left.user",
-1
)
migrateStringPreference(oldPrefs, newPrefs, "action_rightApp", "action.right.app", "")
migrateIntPreference(
oldPrefs,
newPrefs,
"action_rightApp_user",
"action.right.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_right_topApp",
"action.right_top.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_right_topApp_user",
"action.right_top.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_right_bottomApp",
"action.right_bottom.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_right_bottomApp_user",
"action.right_bottom.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_doubleRightApp",
"action.double_right.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_doubleRightApp_user",
"action.double_right.user",
-1
)
migrateBooleanPreference(oldPrefs, newPrefs, "timeVisible", "clock.time_visible", true)
migrateBooleanPreference(oldPrefs, newPrefs, "dateVisible", "clock.date_visible", true)
migrateBooleanPreference(
oldPrefs,
newPrefs,
"dateLocalized",
"clock.date_localized",
false
)
migrateBooleanPreference(
oldPrefs,
newPrefs,
"dateTimeFlip",
"clock.date_time_flip",
false
)
migrateBooleanPreference(
oldPrefs,
newPrefs,
"disableTimeout",
"display.disable_timeout",
false
)
migrateBooleanPreference(
oldPrefs,
newPrefs,
"useFullScreen",
"display.use_full_screen",
true
)
migrateBooleanPreference(
oldPrefs,
newPrefs,
"enableDoubleActions",
"enabled_gestures.double_actions",
true
)
migrateBooleanPreference(
oldPrefs,
newPrefs,
"enableEdgeActions",
"enabled_gestures.edge_actions",
true
)
migrateBooleanPreference(
oldPrefs,
newPrefs,
"searchAutoLaunch",
"functionality.search_auto_launch",
true
)
migrateBooleanPreference(
oldPrefs,
newPrefs,
"searchAutoKeyboard",
"functionality.search_auto_keyboard",
true
)
newPrefs.apply()
when (oldPrefs.getString("theme", "finn")) {
"finn" -> {
LauncherPreferences.theme().colorTheme(ColorTheme.DEFAULT)
LauncherPreferences.theme().monochromeIcons(false)
LauncherPreferences.theme().background(Background.DIM)
}
"dark" -> {
LauncherPreferences.theme().colorTheme(ColorTheme.DARK)
LauncherPreferences.theme().monochromeIcons(true)
LauncherPreferences.theme().background(Background.DIM)
}
}
LauncherPreferences.internal().versionCode(PREFERENCE_VERSION)
Log.i(TAG, "migration of preferences complete.") Log.i(TAG, "migration of preferences complete.")
}
// show the new tutorial 1 -> {
// context.startActivity(Intent(context, TutorialActivity::class.java)) migratePreferencesFromVersion1()
Log.i(TAG, "migration of preferences complete.")
} }
else -> {} else -> {
Log.w(
TAG,
"Shared preferences were written by a newer version of the app (${
LauncherPreferences.internal().versionCode()
})!"
)
}
} }
} }

View file

@ -0,0 +1,94 @@
package de.jrpie.android.launcher.preferences.legacy
import de.jrpie.android.launcher.actions.Action
import de.jrpie.android.launcher.actions.AppAction
import de.jrpie.android.launcher.actions.Gesture
import de.jrpie.android.launcher.actions.LauncherAction
import de.jrpie.android.launcher.apps.AppInfo
import de.jrpie.android.launcher.apps.AppInfo.Companion.INVALID_USER
import de.jrpie.android.launcher.preferences.LauncherPreferences
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
val oldLauncherActionIds: Map<String, LauncherAction> =
mapOf(
Pair("launcher:settings", LauncherAction.SETTINGS),
Pair("launcher:choose", LauncherAction.CHOOSE),
Pair("launcher:chooseFromFavorites", LauncherAction.CHOOSE_FROM_FAVORITES),
Pair("launcher:volumeUp", LauncherAction.VOLUME_UP),
Pair("launcher:volumeDown", LauncherAction.VOLUME_DOWN),
Pair("launcher:nextTrack", LauncherAction.TRACK_NEXT),
Pair("launcher:previousTrack", LauncherAction.TRACK_PREV),
Pair("launcher:expandNotificationsPanel", LauncherAction.EXPAND_NOTIFICATIONS_PANEL),
Pair("launcher:expandSettingsPanel", LauncherAction.EXPAND_SETTINGS_PANEL),
Pair("launcher:lockScreen", LauncherAction.LOCK_SCREEN),
Pair("launcher:toggleTorch", LauncherAction.TORCH),
Pair("launcher:nop", LauncherAction.NOP),
)
private fun AppInfo.Companion.legacyDeserialize(serialized: String): AppInfo {
val values = serialized.split(";")
val packageName = values[0]
val user = Integer.valueOf(values[1])
val activityName = values.getOrNull(2) ?: "" // TODO
return AppInfo(packageName, activityName, user)
}
/**
* Get an action for a specific id.
* An id is of the form:
* - "launcher:${launcher_action_name}", see [LauncherAction]
* - "${package_name}", see [AppAction]
* - "${package_name}:${activity_name}", see [AppAction]
*
* @param id
* @param user a user id, ignored if the action is a [LauncherAction].
*/
private fun Action.Companion.fromId(id: String, user: Int?): Action? {
if (id.isEmpty()) {
return null
}
oldLauncherActionIds[id]?.let { return it }
val values = id.split(";")
return AppAction(
AppInfo(
values[0], values.getOrNull(1) ?: "", user ?: INVALID_USER
)
)
}
private fun Action.Companion.legacyFromPreference(id: String): Action? {
val preferences = LauncherPreferences.getSharedPreferences()
val actionId = preferences.getString("$id.app", "")!!
var u: Int? = preferences.getInt(
"$id.user",
AppInfo.INVALID_USER
)
u = if (u == AppInfo.INVALID_USER) null else u
return Action.fromId(actionId, u)
}
private fun migrateAppInfoSet(key: String) {
(LauncherPreferences.getSharedPreferences().getStringSet(key, setOf()) ?: return)
.map(AppInfo.Companion::legacyDeserialize)
.map(AppInfo::serialize)
.toSet()
.let { LauncherPreferences.getSharedPreferences().edit().putStringSet(key, it).apply() }
}
private fun migrateAction(key: String) {
Action.legacyFromPreference(key)?.let { action ->
LauncherPreferences.getSharedPreferences().edit()
.putString(key, Json.encodeToString(action)).apply()
}
}
fun migratePreferencesFromVersion1() {
Gesture.entries.forEach { g -> migrateAction(g.id) }
migrateAppInfoSet(LauncherPreferences.apps().keys().hidden())
migrateAppInfoSet(LauncherPreferences.apps().keys().favorites())
}

View file

@ -0,0 +1,390 @@
package de.jrpie.android.launcher.preferences.legacy
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import de.jrpie.android.launcher.preferences.LauncherPreferences
import de.jrpie.android.launcher.preferences.theme.Background
import de.jrpie.android.launcher.preferences.theme.ColorTheme
private fun migrateStringPreference(
oldPrefs: SharedPreferences,
newPreferences: SharedPreferences.Editor,
oldKey: String,
newKey: String,
default: String
) {
val s = oldPrefs.getString(oldKey, default)
newPreferences.putString(newKey, s)
}
private fun migrateIntPreference(
oldPrefs: SharedPreferences,
newPreferences: SharedPreferences.Editor,
oldKey: String,
newKey: String,
default: Int
) {
val s = oldPrefs.getInt(oldKey, default)
newPreferences.putInt(newKey, s)
}
private fun migrateBooleanPreference(
oldPrefs: SharedPreferences,
newPreferences: SharedPreferences.Editor,
oldKey: String,
newKey: String,
default: Boolean
) {
val s = oldPrefs.getBoolean(oldKey, default)
newPreferences.putBoolean(newKey, s)
}
private const val TAG = "Preferences ? -> 1"
fun migratePreferencesFromVersionUnknown(context: Context) {
Log.i(
TAG,
"Unknown preference version, trying to restore preferences from old version."
)
val oldPrefs = context.getSharedPreferences(
"V3RYR4ND0MK3YCR4P",
Context.MODE_PRIVATE
)
if (!oldPrefs.contains("startedBefore")) {
Log.i(TAG, "No old preferences found. Probably this is a fresh installation.")
return
}
val newPrefs = LauncherPreferences.getSharedPreferences().edit()
migrateBooleanPreference(
oldPrefs,
newPrefs,
"startedBefore",
"internal.started_before",
false
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_volumeUpApp",
"action.volume_up.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_volumeUpApp_user",
"action.volume_up.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_volumeDownApp",
"action.volume_down.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_volumeDownApp_user",
"action.volume_down.user",
-1
)
migrateStringPreference(oldPrefs, newPrefs, "action_timeApp", "action.time.app", "")
migrateIntPreference(oldPrefs, newPrefs, "action_timeApp_user", "action.time.user", -1)
migrateStringPreference(oldPrefs, newPrefs, "action_dateApp", "action.date.app", "")
migrateIntPreference(oldPrefs, newPrefs, "action_dateApp_user", "action.date.user", -1)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_longClickApp",
"action.long_click.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_longClickApp_user",
"action.long_click.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_doubleClickApp",
"action.double_click.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_doubleClickApp_user",
"action.double_click.user",
-1
)
migrateStringPreference(oldPrefs, newPrefs, "action_upApp", "action.up.app", "")
migrateIntPreference(oldPrefs, newPrefs, "action_upApp_user", "action.up.user", -1)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_up_leftApp",
"action.up_left.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_up_leftApp_user",
"action.up_left.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_up_rightApp",
"action.up_right.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_up_rightApp_user",
"action.up_right.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_doubleUpApp",
"action.double_up.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_doubleUpApp_user",
"action.double_up.user",
-1
)
migrateStringPreference(oldPrefs, newPrefs, "action_downApp", "action.down.app", "")
migrateIntPreference(oldPrefs, newPrefs, "action_downApp_user", "action.down.user", -1)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_down_leftApp",
"action.down_left.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_down_leftApp_user",
"action.down_left.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_down_rightApp",
"action.down_right.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_down_rightApp_user",
"action.down_right.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_doubleDownApp",
"action.double_down.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_doubleDownApp_user",
"action.double_down.user",
-1
)
migrateStringPreference(oldPrefs, newPrefs, "action_leftApp", "action.left.app", "")
migrateIntPreference(oldPrefs, newPrefs, "action_leftApp_user", "action.left.user", -1)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_left_topApp",
"action.left_top.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_left_topApp_user",
"action.left_top.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_left_bottomApp",
"action.left_bottom.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_left_bottomApp_user",
"action.left_bottom.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_doubleLeftApp",
"action.double_left.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_doubleLeftApp_user",
"action.double_left.user",
-1
)
migrateStringPreference(oldPrefs, newPrefs, "action_rightApp", "action.right.app", "")
migrateIntPreference(
oldPrefs,
newPrefs,
"action_rightApp_user",
"action.right.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_right_topApp",
"action.right_top.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_right_topApp_user",
"action.right_top.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_right_bottomApp",
"action.right_bottom.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_right_bottomApp_user",
"action.right_bottom.user",
-1
)
migrateStringPreference(
oldPrefs,
newPrefs,
"action_doubleRightApp",
"action.double_right.app",
""
)
migrateIntPreference(
oldPrefs,
newPrefs,
"action_doubleRightApp_user",
"action.double_right.user",
-1
)
migrateBooleanPreference(oldPrefs, newPrefs, "timeVisible", "clock.time_visible", true)
migrateBooleanPreference(oldPrefs, newPrefs, "dateVisible", "clock.date_visible", true)
migrateBooleanPreference(
oldPrefs,
newPrefs,
"dateLocalized",
"clock.date_localized",
false
)
migrateBooleanPreference(
oldPrefs,
newPrefs,
"dateTimeFlip",
"clock.date_time_flip",
false
)
migrateBooleanPreference(
oldPrefs,
newPrefs,
"disableTimeout",
"display.disable_timeout",
false
)
migrateBooleanPreference(
oldPrefs,
newPrefs,
"useFullScreen",
"display.use_full_screen",
true
)
migrateBooleanPreference(
oldPrefs,
newPrefs,
"enableDoubleActions",
"enabled_gestures.double_actions",
true
)
migrateBooleanPreference(
oldPrefs,
newPrefs,
"enableEdgeActions",
"enabled_gestures.edge_actions",
true
)
migrateBooleanPreference(
oldPrefs,
newPrefs,
"searchAutoLaunch",
"functionality.search_auto_launch",
true
)
migrateBooleanPreference(
oldPrefs,
newPrefs,
"searchAutoKeyboard",
"functionality.search_auto_keyboard",
true
)
newPrefs.apply()
when (oldPrefs.getString("theme", "finn")) {
"finn" -> {
LauncherPreferences.theme().colorTheme(ColorTheme.DEFAULT)
LauncherPreferences.theme().monochromeIcons(false)
LauncherPreferences.theme().background(Background.DIM)
}
"dark" -> {
LauncherPreferences.theme().colorTheme(ColorTheme.DARK)
LauncherPreferences.theme().monochromeIcons(true)
LauncherPreferences.theme().background(Background.DIM)
}
}
LauncherPreferences.internal().versionCode(1)
Log.i(TAG, "migrated preferences to version 1.")
migratePreferencesFromVersion1()
}

View file

@ -5,145 +5,145 @@
<!-- Swipe up - Apps list --> <!-- Swipe up - Apps list -->
<string-array name="default_up"> <string-array name="default_up">
<item>launcher:choose</item> <!-- All Apps --> <item>{\"type\": \"action:launcher\", \"id\": \"choose\"}</item> <!-- All Apps -->
</string-array> </string-array>
<!-- Swipe up (left edge) - Favorite Apps --> <!-- Swipe up (left edge) - Favorite Apps -->
<string-array name="default_up_left"> <string-array name="default_up_left">
<item>launcher:chooseFromFavorites</item> <item>{\"type\": \"action:launcher\", \"id\": \"choose_from_favorites\"}</item>
</string-array> </string-array>
<!-- Swipe up (right edge) - Maps --> <!-- Swipe up (right edge) - Maps -->
<string-array name="default_up_right"> <string-array name="default_up_right">
<item>app.organicmaps</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"app.organicmaps\", \"activityName\": null}}</item>
<item>com.graphhopper.maps</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.graphhopper.maps\", \"activityName\": null}}</item>
<item>net.osmand.plus</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"net.osmand.plus\", \"activityName\": null}}</item>
<item>com.google.android.apps.maps</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.google.android.apps.maps\", \"activityName\": null}}</item>
</string-array> </string-array>
<!-- Swipe double up - Transportation --> <!-- Swipe double up - Transportation -->
<string-array name="default_double_up"> <string-array name="default_double_up">
<item>de.schildbach.oeffi</item> <!-- Öffi --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"de.schildbach.oeffi\", \"activityName\": null}}</item> <!-- Öffi -->
<item>de.hafas.android.db</item> <!-- DB Navigator --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"de.hafas.android.db\", \"activityName\": null}}</item> <!-- DB Navigator -->
</string-array> </string-array>
<!-- Swipe down - Browser --> <!-- Swipe down - Browser -->
<string-array name="default_down"> <string-array name="default_down">
<item>org.mozilla.firefox</item> <!-- Firefox --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.mozilla.firefox\", \"activityName\": null}}</item> <!-- Firefox -->
<item>org.mozilla.fennec</item> <!-- Fennec --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.mozilla.fennec\", \"activityName\": null}}</item> <!-- Fennec -->
<item>org.mozilla.fennec_fdroid</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.mozilla.fennec_fdroid\", \"activityName\": null}}</item>
<item>com.brave.browser</item> <!-- Brave Browser --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.brave.browser\", \"activityName\": null}}</item> <!-- Brave Browser -->
<item>com.duckduckgo.mobile.android</item> <!-- DuckDuckGo Browser --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.duckduckgo.mobile.android\", \"activityName\": null}}</item> <!-- DuckDuckGo Browser -->
<item>com.sec.android.app.sbrowser</item> <!-- Samsung Internet --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.sec.android.app.sbrowser\", \"activityName\": null}}</item> <!-- Samsung Internet -->
<item>com.android.chrome</item> <!-- Chrome --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.android.chrome\", \"activityName\": null}}</item> <!-- Chrome -->
</string-array> </string-array>
<!-- Swipe down (left edge) - Translation --> <!-- Swipe down (left edge) - Translation -->
<string-array name="default_down_left"> <string-array name="default_down_left">
<item>com.bnyro.translate</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.bnyro.translate\", \"activityName\": null}}</item>
<item>com.deepl.mobiletranslator</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.deepl.mobiletranslator\", \"activityName\": null}}</item>
<item>com.google.android.apps.translate</item> <!-- Google Translate --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.google.android.apps.translate\", \"activityName\": null}}</item> <!-- Google Translate -->
<item>com.microsoft.translator</item> <!-- Microsoft Translate --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.microsoft.translator\", \"activityName\": null}}</item> <!-- Microsoft Translate -->
<item>translate.speech.text.translation.voicetranslator</item> <!-- MindMover Translate --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"translate.speech.text.translation.voicetranslator\", \"activityName\": null}}</item> <!-- MindMover Translate -->
</string-array> </string-array>
<string-array name="default_down_right"> <string-array name="default_down_right">
<item>org.fdroid.fdroid</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.fdroid.fdroid\", \"activityName\": null}}</item>
<item>org.fdroid.basic</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.fdroid.basic\", \"activityName\": null}}</item>
<item>dev.imranr.obtainium</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"dev.imranr.obtainium\", \"activityName\": null}}</item>
<item>dev.imranr.obtainium.fdroid</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"dev.imranr.obtainium.fdroid\", \"activityName\": null}}</item>
</string-array> </string-array>
<!-- Swipe double down - Secure Browser --> <!-- Swipe double down - Secure Browser -->
<string-array name="default_double_down"> <string-array name="default_double_down">
<item>org.torproject.torbrowser</item> <!-- Tor Browser --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.torproject.torbrowser\", \"activityName\": null}}</item> <!-- Tor Browser -->
</string-array> </string-array>
<!-- Swipe right - Mail --> <!-- Swipe right - Mail -->
<string-array name="default_right"> <string-array name="default_right">
<item>net.thunderbird.android</item> <!-- Thunderbird --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"net.thunderbird.android\", \"activityName\": null}}</item> <!-- Thunderbird -->
<item>com.fsck.k9</item> <!-- k9-mail --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.fsck.k9\", \"activityName\": null}}</item> <!-- k9-mail -->
<item>de.web.mobile.android.mail</item> <!-- WebMail --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"de.web.mobile.android.mail\", \"activityName\": null}}</item> <!-- WebMail -->
<item>com.samsung.android.email.provider</item> <!-- Samsung Mail --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.samsung.android.email.provider\", \"activityName\": null}}</item> <!-- Samsung Mail -->
<item>com.google.android.gm</item> <!-- Google Mail --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.google.android.gm\", \"activityName\": null}}</item> <!-- Google Mail -->
</string-array> </string-array>
<!-- Swipe right (top) --> <!-- Swipe right (top) -->
<string-array name="default_right_top"> <string-array name="default_right_top">
<item>com.android.calculator2</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.android.calculator2\", \"activityName\": null}}</item>
<item>com.sec.android.app.popupcalculator</item> <!-- Samsung Calculator --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.sec.android.app.popupcalculator\", \"activityName\": null}}</item> <!-- Samsung Calculator -->
<item>org.mian.gitnex</item> <!-- GitNex --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.mian.gitnex\", \"activityName\": null}}</item> <!-- GitNex -->
<item>com.github.android</item> <!-- GitHub Android --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.github.android\", \"activityName\": null}}</item> <!-- GitHub Android -->
</string-array> </string-array>
<!-- Swipe right (bottom) --> <!-- Swipe right (bottom) -->
<string-array name="default_right_bottom"> <string-array name="default_right_bottom">
<item>com.android.documentsui</item> <!-- File Manager --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.android.documentsui\", \"activityName\": null}}</item> <!-- File Manager -->
<item>com.android.google.documentsui</item> <!-- File Manager --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.android.google.documentsui\", \"activityName\": null}}</item> <!-- File Manager -->
</string-array> </string-array>
<!-- Swipe double right --> <!-- Swipe double right -->
<string-array name="default_double_right"> <string-array name="default_double_right">
<item>be.chvp.nanoledger</item> <!-- NanoLedger --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"be.chvp.nanoledger\", \"activityName\": null}}</item> <!-- NanoLedger -->
<item>info.tangential.cone</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"info.tangential.cone\", \"activityName\": null}}</item>
</string-array> </string-array>
<string-array name="default_messengers"> <string-array name="default_messengers">
<item>de.spiritcroc.riotx</item> <!-- SchildiChat --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"de.spiritcroc.riotx\", \"activityName\": null}}</item> <!-- SchildiChat -->
<item>io.element.android.x</item> <!-- Element X --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"io.element.android.x\", \"activityName\": null}}</item> <!-- Element X -->
<item>im.vector.app</item> <!-- Element --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"im.vector.app\", \"activityName\": null}}</item> <!-- Element -->
<item>org.thoughtcrime.securesms</item> <!-- Signal --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.thoughtcrime.securesms\", \"activityName\": null}}</item> <!-- Signal -->
<item>org.briarproject.briar.android</item> <!-- Briar --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.briarproject.briar.android\", \"activityName\": null}}</item> <!-- Briar -->
<item>eu.siacs.conversations</item> <!-- Conversations --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"eu.siacs.conversations\", \"activityName\": null}}</item> <!-- Conversations -->
<item>ch.threema.app.libre</item> <!-- Threema --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"ch.threema.app.libre\", \"activityName\": null}}</item> <!-- Threema -->
<item>com.android.messaging</item> <!-- SMS --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.android.messaging\", \"activityName\": null}}</item> <!-- SMS -->
<item>com.google.android.apps.messaging</item> <!-- SMS --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.google.android.apps.messaging\", \"activityName\": null}}</item> <!-- SMS -->
<item>com.samsung.android.messaging</item> <!-- Samsung SMS --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.samsung.android.messaging\", \"activityName\": null}}</item> <!-- Samsung SMS -->
<item>com.whatsapp</item> <!-- WhatsApp --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.whatsapp\", \"activityName\": null}}</item> <!-- WhatsApp -->
<item>org.telegram.messenger</item> <!-- Telegram --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.telegram.messenger\", \"activityName\": null}}</item> <!-- Telegram -->
<item>com.discord</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.discord\", \"activityName\": null}}</item>
</string-array> </string-array>
<!-- Volume up --> <!-- Volume up -->
<string-array name="default_volume_up"> <string-array name="default_volume_up">
<item>launcher:volumeUp</item> <item>{\"type\": \"action:launcher\", \"id\": \"volume_up\"}</item>
</string-array> </string-array>
<!-- Volume down --> <!-- Volume down -->
<string-array name="default_volume_down"> <string-array name="default_volume_down">
<item>launcher:volumeDown</item> <item>{\"type\": \"action:launcher\", \"id\": \"volume_down\"}</item>
</string-array> </string-array>
<!-- Double click - Notes --> <!-- Double click - Notes -->
<string-array name="default_double_click"> <string-array name="default_double_click">
<item>it.niedermann.owncloud.notes</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"it.niedermann.owncloud.notes\", \"activityName\": null}}</item>
<item>com.samsung.android.app.notes</item> <!-- Samsung Notes --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.samsung.android.app.notes\", \"activityName\": null}}</item> <!-- Samsung Notes -->
<item>com.sec.android.widgetapp.diotek.smemo</item> <!-- S Memo (older devices) --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.sec.android.widgetapp.diotek.smemo\", \"activityName\": null}}</item> <!-- S Memo (older devices) -->
<item>launcher:lockScreen</item> <item>{\"type\": \"action:launcher\", \"id\": \"lock_screen\"}</item>
</string-array> </string-array>
<!-- Long click - Security --> <!-- Long click - Security -->
<string-array name="default_long_click"> <string-array name="default_long_click">
<item>com.beemdevelopment.aegis</item> <!-- Aegis 2FA --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.beemdevelopment.aegis\", \"activityName\": null}}</item> <!-- Aegis 2FA -->
<item>org.fedorahosted.freeotp</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.fedorahosted.freeotp\", \"activityName\": null}}</item>
<item>proton.android.pass.fdroid</item> <!-- Proton Pass --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"proton.android.pass.fdroid\", \"activityName\": null}}</item> <!-- Proton Pass -->
<item>com.kunzisoft.keepass.libre</item> <!-- KeePassDX --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.kunzisoft.keepass.libre\", \"activityName\": null}}</item> <!-- KeePassDX -->
<item>launcher:settings</item> <!-- Launcher Settings --> <item>{\"type\": \"action:launcher\", \"id\": \"settings\"}</item> <!-- Launcher Settings -->
</string-array> </string-array>
<!-- Time / Clock --> <!-- Time / Clock -->
<string-array name="default_time"> <string-array name="default_time">
<item>com.android.deskclock</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.android.deskclock\", \"activityName\": null}}</item>
<item>com.google.android.deskclock</item> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.google.android.deskclock\", \"activityName\": null}}</item>
<item>com.sec.android.app.clockpackage</item> <!-- Android Clock --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.sec.android.app.clockpackage\", \"activityName\": null}}</item> <!-- Android Clock -->
</string-array> </string-array>
<!-- Date / Calendar --> <!-- Date / Calendar -->
<string-array name="default_date"> <string-array name="default_date">
<item>org.lineageos.etar</item> <!-- LineageOS Calendar --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.lineageos.etar\", \"activityName\": null}}</item> <!-- LineageOS Calendar -->
<item>ws.xsoh.etar</item> <!-- Etar --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"ws.xsoh.etar\", \"activityName\": null}}</item> <!-- Etar -->
<item>com.google.android.calendar</item> <!-- Google Calendar --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.google.android.calendar\", \"activityName\": null}}</item> <!-- Google Calendar -->
<item>com.samsung.android.calendar</item> <!-- Samsung Calendar --> <item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.samsung.android.calendar\", \"activityName\": null}}</item> <!-- Samsung Calendar -->
</string-array> </string-array>
</resources> </resources>

View file

@ -13,6 +13,8 @@ buildscript {
classpath 'com.android.tools.build:gradle:8.7.3' classpath 'com.android.tools.build:gradle:8.7.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.android.tools.build:gradle:$android_plugin_version" classpath "com.android.tools.build:gradle:$android_plugin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files