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: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlinx-serialization'
android {
dataBinding {
enabled = true
}
@ -76,6 +76,7 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.preference:preference-ktx:1.2.1'
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"
annotationProcessor "eu.jonahbauer:android-preference-annotations:1.1.2"
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.widget.Toast
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 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 bindToGesture(prefEditor: Editor, id: String)
fun label(context: Context): String
fun getIcon(context: Context): Drawable?
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 {
/**
* 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? {
val id = gesture.id
val preferences = LauncherPreferences.getSharedPreferences()
val actionId = preferences.getString("$id.app", "")!!
var u: Int? = preferences.getInt("$id.user", INVALID_USER)
u = if (u == INVALID_USER) null else u
return fromId(actionId, u)
val json = preferences.getString(id, "null")!!
return Json.decodeFromString(json)
}
fun resetToDefaultActions(context: Context) {
@ -72,11 +47,11 @@ interface Action {
context.resources
.getStringArray(gesture.defaultsResource)
.filterNot { boundActions.contains(it) }
.map { Pair(it, fromId(it, null, context)) }
.firstOrNull { it.second?.isAvailable(context) ?: false }
.map { Pair(it, Json.decodeFromString<Action>(it)) }
.firstOrNull { it.second.isAvailable(context) }
?.apply {
boundActions.add(first)
second?.bindToGesture(editor, gesture.id)
second.bindToGesture(editor, gesture.id)
}
}
editor.apply()
@ -94,8 +69,7 @@ interface Action {
fun clearActionForGesture(gesture: Gesture) {
LauncherPreferences.getSharedPreferences().edit()
.putString(gesture.id + ".app", "")
.putInt(gesture.id + ".user", INVALID_USER)
.remove(gesture.id)
.apply()
}
@ -119,9 +93,8 @@ interface Action {
}
fun fromIntent(data: Intent): Action? {
val value = data.getStringExtra("action_id") ?: return null
val user = data.getIntExtra("user", INVALID_USER)
return fromId(value, user)
val json = data.getStringExtra("action") ?: return null
return Json.decodeFromString(json)
}
}
}

View file

@ -4,7 +4,6 @@ import android.app.AlertDialog
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.LauncherApps
import android.graphics.Rect
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.DetailedAppInfo
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 {
val packageName = appInfo.packageName.toString()
if (appInfo.user != INVALID_USER) {
val packageName = app.packageName
if (app.user != INVALID_USER) {
val launcherApps =
context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps
appInfo.getLauncherActivityInfo(context)?.let { app ->
Log.i("Launcher", "Starting $appInfo")
app.getLauncherActivityInfo(context)?.let { app ->
Log.i("Launcher", "Starting ${this.app}")
launcherApps.startMainActivity(app.componentName, app.user, rect, null)
return true
}
@ -44,7 +47,7 @@ class AppAction(val appInfo: AppInfo) : Action {
.setTitle(context.getString(R.string.alert_cant_open_title))
.setMessage(context.getString(R.string.alert_cant_open_message))
.setPositiveButton(android.R.string.ok) { _, _ ->
appInfo.openSettings(context)
app.openSettings(context)
}
.setNegativeButton(android.R.string.cancel, null)
.setIcon(android.R.drawable.ic_dialog_info)
@ -55,33 +58,15 @@ class AppAction(val appInfo: AppInfo) : Action {
}
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? {
return DetailedAppInfo.fromAppInfo(appInfo, context)?.icon
return DetailedAppInfo.fromAppInfo(app, context)?.icon
}
override fun isAvailable(context: Context): Boolean {
// check if app is installed
return DetailedAppInfo.fromAppInfo(appInfo, 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)
return DetailedAppInfo.fromAppInfo(app, context) != null
}
}

View file

@ -2,7 +2,6 @@ package de.jrpie.android.launcher.actions
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences.Editor
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.media.AudioManager
@ -13,11 +12,20 @@ import android.widget.Toast
import de.jrpie.android.launcher.Application
import de.jrpie.android.launcher.R
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.ui.list.ListActivity
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(
val id: String,
val label: Int,
@ -25,68 +33,68 @@ enum class LauncherAction(
val launch: (Context) -> Unit
) : Action {
SETTINGS(
"launcher:settings",
"settings",
R.string.list_other_settings,
R.drawable.baseline_settings_24,
::openSettings
),
CHOOSE(
"launcher:choose",
"choose",
R.string.list_other_list,
R.drawable.baseline_menu_24,
::openAppsList
),
CHOOSE_FROM_FAVORITES(
"launcher:chooseFromFavorites",
"choose_from_favorites",
R.string.list_other_list_favorites,
R.drawable.baseline_favorite_24,
{ context -> openAppsList(context, true) }
),
VOLUME_UP(
"launcher:volumeUp",
"volume_up",
R.string.list_other_volume_up,
R.drawable.baseline_volume_up_24, ::audioVolumeUp
),
VOLUME_DOWN(
"launcher:volumeDown",
"volume_down",
R.string.list_other_volume_down,
R.drawable.baseline_volume_down_24, ::audioVolumeDown
),
TRACK_NEXT(
"launcher:nextTrack",
"next_track",
R.string.list_other_track_next,
R.drawable.baseline_skip_next_24, ::audioNextTrack
),
TRACK_PREV(
"launcher:previousTrack",
"previous_track",
R.string.list_other_track_previous,
R.drawable.baseline_skip_previous_24, ::audioPreviousTrack
),
EXPAND_NOTIFICATIONS_PANEL(
"launcher:expandNotificationsPanel",
"expand_notifications_panel",
R.string.list_other_expand_notifications_panel,
R.drawable.baseline_notifications_24,
::expandNotificationsPanel
),
EXPAND_SETTINGS_PANEL(
"launcher:expandSettingsPanel",
"expand_settings_panel",
R.string.list_other_expand_settings_panel,
R.drawable.baseline_settings_applications_24,
::expandSettingsPanel
),
LOCK_SCREEN(
"launcher:lockScreen",
"lock_screen",
R.string.list_other_lock_screen,
R.drawable.baseline_lock_24px,
{ c -> LauncherPreferences.actions().lockMethod().lockOrEnable(c) }
),
TORCH(
"launcher:toggleTorch",
"toggle_torch",
R.string.list_other_torch,
R.drawable.baseline_flashlight_on_24,
::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 {
launch(context)
@ -101,16 +109,6 @@ enum class LauncherAction(
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 {
return true
}
@ -119,10 +117,6 @@ enum class LauncherAction(
fun byId(id: String): LauncherAction? {
return entries.singleOrNull { it.id == id }
}
fun isOtherAction(id: String): Boolean {
return id.startsWith("launcher")
}
}
}
@ -252,4 +246,32 @@ fun openAppsList(context: Context, favorite: Boolean = false, hidden: Boolean =
)
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()) {
val boundApps = Gesture.entries
.filter(Gesture::isEnabled)
.mapNotNull { g -> (Action.forGesture(g) as? AppAction)?.appInfo }
.mapNotNull { g -> (Action.forGesture(g) as? AppAction)?.app }
.toSet()
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.LauncherApps
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.
* Contains the minimal amount of data required to identify the app.
*/
class AppInfo(val packageName: CharSequence, val activityName: CharSequence?, val user: Int = INVALID_USER) {
// TODO: make activityName non nullable (breaking change to SharedPreferences!)
@Serializable
class AppInfo(val packageName: String, val activityName: String?, val user: Int = INVALID_USER) {
fun serialize(): String {
val u = user
var ret = "$packageName;$u"
activityName?.let { ret += ";$activityName" }
return ret
return Json.encodeToString(this)
}
override fun equals(other: Any?): Boolean {
@ -39,7 +37,7 @@ class AppInfo(val packageName: CharSequence, val activityName: CharSequence?, va
): LauncherActivityInfo? {
val launcherApps = context.getSystemService(Service.LAUNCHER_APPS_SERVICE) as LauncherApps
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 }
?: activityList.firstOrNull()
}
@ -53,11 +51,7 @@ class AppInfo(val packageName: CharSequence, val activityName: CharSequence?, va
const val INVALID_USER = -1
fun deserialize(serialized: String): AppInfo {
val values = serialized.split(";")
val packageName = values[0]
val user = Integer.valueOf(values[1])
val activityName = values.getOrNull(2)
return AppInfo(packageName, activityName, user)
return Json.decodeFromString(serialized)
}
}
}

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

View file

@ -1,57 +1,24 @@
package de.jrpie.android.launcher.preferences
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import de.jrpie.android.launcher.BuildConfig
import de.jrpie.android.launcher.actions.Action
import de.jrpie.android.launcher.apps.AppInfo
import de.jrpie.android.launcher.apps.DetailedAppInfo
import de.jrpie.android.launcher.preferences.theme.Background
import de.jrpie.android.launcher.preferences.theme.ColorTheme
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion1
import de.jrpie.android.launcher.ui.HomeActivity
/* Current version of the structure of preferences.
* Increase when breaking changes are introduced and write an appropriate case in
* `migratePreferencesToNewVersion`
*/
const val PREFERENCE_VERSION = 1
const val PREFERENCE_VERSION = 2
const val UNKNOWN_PREFERENCE_VERSION = -1
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) {
when (LauncherPreferences.internal().versionCode()) {
@ -60,356 +27,23 @@ fun migratePreferencesToNewVersion(context: Context) {
}
UNKNOWN_PREFERENCE_VERSION -> { /* still using the old preferences file */
Log.i(
TAG,
"Unknown preference version, trying to restore preferences from old version."
)
migratePreferencesFromVersionUnknown(context)
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.")
// show the new tutorial
// context.startActivity(Intent(context, TutorialActivity::class.java))
}
1 -> {
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 -->
<string-array name="default_up">
<item>launcher:choose</item> <!-- All Apps -->
<item>{\"type\": \"action:launcher\", \"id\": \"choose\"}</item> <!-- All Apps -->
</string-array>
<!-- Swipe up (left edge) - Favorite Apps -->
<string-array name="default_up_left">
<item>launcher:chooseFromFavorites</item>
<item>{\"type\": \"action:launcher\", \"id\": \"choose_from_favorites\"}</item>
</string-array>
<!-- Swipe up (right edge) - Maps -->
<string-array name="default_up_right">
<item>app.organicmaps</item>
<item>com.graphhopper.maps</item>
<item>net.osmand.plus</item>
<item>com.google.android.apps.maps</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"app.organicmaps\", \"activityName\": null}}</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.graphhopper.maps\", \"activityName\": null}}</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"net.osmand.plus\", \"activityName\": null}}</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.google.android.apps.maps\", \"activityName\": null}}</item>
</string-array>
<!-- Swipe double up - Transportation -->
<string-array name="default_double_up">
<item>de.schildbach.oeffi</item> <!-- Öffi -->
<item>de.hafas.android.db</item> <!-- DB Navigator -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"de.schildbach.oeffi\", \"activityName\": null}}</item> <!-- Öffi -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"de.hafas.android.db\", \"activityName\": null}}</item> <!-- DB Navigator -->
</string-array>
<!-- Swipe down - Browser -->
<string-array name="default_down">
<item>org.mozilla.firefox</item> <!-- Firefox -->
<item>org.mozilla.fennec</item> <!-- Fennec -->
<item>org.mozilla.fennec_fdroid</item>
<item>com.brave.browser</item> <!-- Brave Browser -->
<item>com.duckduckgo.mobile.android</item> <!-- DuckDuckGo Browser -->
<item>com.sec.android.app.sbrowser</item> <!-- Samsung Internet -->
<item>com.android.chrome</item> <!-- Chrome -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.mozilla.firefox\", \"activityName\": null}}</item> <!-- Firefox -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.mozilla.fennec\", \"activityName\": null}}</item> <!-- Fennec -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.mozilla.fennec_fdroid\", \"activityName\": null}}</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.brave.browser\", \"activityName\": null}}</item> <!-- Brave Browser -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.duckduckgo.mobile.android\", \"activityName\": null}}</item> <!-- DuckDuckGo Browser -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.sec.android.app.sbrowser\", \"activityName\": null}}</item> <!-- Samsung Internet -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.android.chrome\", \"activityName\": null}}</item> <!-- Chrome -->
</string-array>
<!-- Swipe down (left edge) - Translation -->
<string-array name="default_down_left">
<item>com.bnyro.translate</item>
<item>com.deepl.mobiletranslator</item>
<item>com.google.android.apps.translate</item> <!-- Google Translate -->
<item>com.microsoft.translator</item> <!-- Microsoft Translate -->
<item>translate.speech.text.translation.voicetranslator</item> <!-- MindMover Translate -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.bnyro.translate\", \"activityName\": null}}</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.deepl.mobiletranslator\", \"activityName\": null}}</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.google.android.apps.translate\", \"activityName\": null}}</item> <!-- Google Translate -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.microsoft.translator\", \"activityName\": null}}</item> <!-- Microsoft Translate -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"translate.speech.text.translation.voicetranslator\", \"activityName\": null}}</item> <!-- MindMover Translate -->
</string-array>
<string-array name="default_down_right">
<item>org.fdroid.fdroid</item>
<item>org.fdroid.basic</item>
<item>dev.imranr.obtainium</item>
<item>dev.imranr.obtainium.fdroid</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.fdroid.fdroid\", \"activityName\": null}}</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.fdroid.basic\", \"activityName\": null}}</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"dev.imranr.obtainium\", \"activityName\": null}}</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"dev.imranr.obtainium.fdroid\", \"activityName\": null}}</item>
</string-array>
<!-- Swipe double down - Secure Browser -->
<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>
<!-- Swipe right - Mail -->
<string-array name="default_right">
<item>net.thunderbird.android</item> <!-- Thunderbird -->
<item>com.fsck.k9</item> <!-- k9-mail -->
<item>de.web.mobile.android.mail</item> <!-- WebMail -->
<item>com.samsung.android.email.provider</item> <!-- Samsung Mail -->
<item>com.google.android.gm</item> <!-- Google Mail -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"net.thunderbird.android\", \"activityName\": null}}</item> <!-- Thunderbird -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.fsck.k9\", \"activityName\": null}}</item> <!-- k9-mail -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"de.web.mobile.android.mail\", \"activityName\": null}}</item> <!-- WebMail -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.samsung.android.email.provider\", \"activityName\": null}}</item> <!-- Samsung Mail -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.google.android.gm\", \"activityName\": null}}</item> <!-- Google Mail -->
</string-array>
<!-- Swipe right (top) -->
<string-array name="default_right_top">
<item>com.android.calculator2</item>
<item>com.sec.android.app.popupcalculator</item> <!-- Samsung Calculator -->
<item>org.mian.gitnex</item> <!-- GitNex -->
<item>com.github.android</item> <!-- GitHub Android -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.android.calculator2\", \"activityName\": null}}</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.sec.android.app.popupcalculator\", \"activityName\": null}}</item> <!-- Samsung Calculator -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.mian.gitnex\", \"activityName\": null}}</item> <!-- GitNex -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.github.android\", \"activityName\": null}}</item> <!-- GitHub Android -->
</string-array>
<!-- Swipe right (bottom) -->
<string-array name="default_right_bottom">
<item>com.android.documentsui</item> <!-- File Manager -->
<item>com.android.google.documentsui</item> <!-- File Manager -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.android.documentsui\", \"activityName\": null}}</item> <!-- File Manager -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.android.google.documentsui\", \"activityName\": null}}</item> <!-- File Manager -->
</string-array>
<!-- Swipe double right -->
<string-array name="default_double_right">
<item>be.chvp.nanoledger</item> <!-- NanoLedger -->
<item>info.tangential.cone</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"be.chvp.nanoledger\", \"activityName\": null}}</item> <!-- NanoLedger -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"info.tangential.cone\", \"activityName\": null}}</item>
</string-array>
<string-array name="default_messengers">
<item>de.spiritcroc.riotx</item> <!-- SchildiChat -->
<item>io.element.android.x</item> <!-- Element X -->
<item>im.vector.app</item> <!-- Element -->
<item>org.thoughtcrime.securesms</item> <!-- Signal -->
<item>org.briarproject.briar.android</item> <!-- Briar -->
<item>eu.siacs.conversations</item> <!-- Conversations -->
<item>ch.threema.app.libre</item> <!-- Threema -->
<item>com.android.messaging</item> <!-- SMS -->
<item>com.google.android.apps.messaging</item> <!-- SMS -->
<item>com.samsung.android.messaging</item> <!-- Samsung SMS -->
<item>com.whatsapp</item> <!-- WhatsApp -->
<item>org.telegram.messenger</item> <!-- Telegram -->
<item>com.discord</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"de.spiritcroc.riotx\", \"activityName\": null}}</item> <!-- SchildiChat -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"io.element.android.x\", \"activityName\": null}}</item> <!-- Element X -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"im.vector.app\", \"activityName\": null}}</item> <!-- Element -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.thoughtcrime.securesms\", \"activityName\": null}}</item> <!-- Signal -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.briarproject.briar.android\", \"activityName\": null}}</item> <!-- Briar -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"eu.siacs.conversations\", \"activityName\": null}}</item> <!-- Conversations -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"ch.threema.app.libre\", \"activityName\": null}}</item> <!-- Threema -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.android.messaging\", \"activityName\": null}}</item> <!-- SMS -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.google.android.apps.messaging\", \"activityName\": null}}</item> <!-- SMS -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.samsung.android.messaging\", \"activityName\": null}}</item> <!-- Samsung SMS -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.whatsapp\", \"activityName\": null}}</item> <!-- WhatsApp -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.telegram.messenger\", \"activityName\": null}}</item> <!-- Telegram -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.discord\", \"activityName\": null}}</item>
</string-array>
<!-- Volume up -->
<string-array name="default_volume_up">
<item>launcher:volumeUp</item>
<item>{\"type\": \"action:launcher\", \"id\": \"volume_up\"}</item>
</string-array>
<!-- Volume down -->
<string-array name="default_volume_down">
<item>launcher:volumeDown</item>
<item>{\"type\": \"action:launcher\", \"id\": \"volume_down\"}</item>
</string-array>
<!-- Double click - Notes -->
<string-array name="default_double_click">
<item>it.niedermann.owncloud.notes</item>
<item>com.samsung.android.app.notes</item> <!-- Samsung Notes -->
<item>com.sec.android.widgetapp.diotek.smemo</item> <!-- S Memo (older devices) -->
<item>launcher:lockScreen</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"it.niedermann.owncloud.notes\", \"activityName\": null}}</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.samsung.android.app.notes\", \"activityName\": null}}</item> <!-- Samsung Notes -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.sec.android.widgetapp.diotek.smemo\", \"activityName\": null}}</item> <!-- S Memo (older devices) -->
<item>{\"type\": \"action:launcher\", \"id\": \"lock_screen\"}</item>
</string-array>
<!-- Long click - Security -->
<string-array name="default_long_click">
<item>com.beemdevelopment.aegis</item> <!-- Aegis 2FA -->
<item>org.fedorahosted.freeotp</item>
<item>proton.android.pass.fdroid</item> <!-- Proton Pass -->
<item>com.kunzisoft.keepass.libre</item> <!-- KeePassDX -->
<item>launcher:settings</item> <!-- Launcher Settings -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.beemdevelopment.aegis\", \"activityName\": null}}</item> <!-- Aegis 2FA -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.fedorahosted.freeotp\", \"activityName\": null}}</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"proton.android.pass.fdroid\", \"activityName\": null}}</item> <!-- Proton Pass -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.kunzisoft.keepass.libre\", \"activityName\": null}}</item> <!-- KeePassDX -->
<item>{\"type\": \"action:launcher\", \"id\": \"settings\"}</item> <!-- Launcher Settings -->
</string-array>
<!-- Time / Clock -->
<string-array name="default_time">
<item>com.android.deskclock</item>
<item>com.google.android.deskclock</item>
<item>com.sec.android.app.clockpackage</item> <!-- Android Clock -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.android.deskclock\", \"activityName\": null}}</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.google.android.deskclock\", \"activityName\": null}}</item>
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.sec.android.app.clockpackage\", \"activityName\": null}}</item> <!-- Android Clock -->
</string-array>
<!-- Date / Calendar -->
<string-array name="default_date">
<item>org.lineageos.etar</item> <!-- LineageOS Calendar -->
<item>ws.xsoh.etar</item> <!-- Etar -->
<item>com.google.android.calendar</item> <!-- Google Calendar -->
<item>com.samsung.android.calendar</item> <!-- Samsung Calendar -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"org.lineageos.etar\", \"activityName\": null}}</item> <!-- LineageOS Calendar -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"ws.xsoh.etar\", \"activityName\": null}}</item> <!-- Etar -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.google.android.calendar\", \"activityName\": null}}</item> <!-- Google Calendar -->
<item>{\"type\": \"action:app\", \"app\": {\"packageName\": \"com.samsung.android.calendar\", \"activityName\": null}}</item> <!-- Samsung Calendar -->
</string-array>
</resources>

View file

@ -13,6 +13,8 @@ buildscript {
classpath 'com.android.tools.build:gradle:8.7.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_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
// in the individual module build.gradle files