From 49ae7e130f78c3472957a451bc21c160add2fa81 Mon Sep 17 00:00:00 2001 From: Josia Pietsch Date: Tue, 1 Oct 2024 19:37:12 +0200 Subject: [PATCH] some tests --- app/src/main/AndroidManifest.xml | 1 + .../de/jrpie/android/launcher/Application.kt | 21 ++- .../de/jrpie/android/launcher/Functions.kt | 5 +- .../LauncherPreferences$Config.java | 2 + .../serialization/PreferenceSerializers.kt | 19 +++ .../jrpie/android/launcher/ui/HomeActivity.kt | 20 +++ .../ui/widgets/WidgetContainerView.kt | 12 ++ .../android/launcher/widgets/WidgetInfo.kt | 28 ++++ .../jrpie/android/launcher/widgets/Widgets.kt | 123 ++++++++++++++++++ app/src/main/res/layout/home.xml | 9 ++ app/src/main/res/values/donottranslate.xml | 1 + 11 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetContainerView.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/widgets/WidgetInfo.kt create mode 100644 app/src/main/java/de/jrpie/android/launcher/widgets/Widgets.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a5f8831..4ab7065 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ tools:ignore="QueryAllPackagesPermission" /> + >() val privateSpaceLocked = MutableLiveData() + lateinit var appWidgetHost: AppWidgetHost + lateinit var appWidgetManager: AppWidgetManager private val profileAvailabilityBroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { @@ -103,10 +111,15 @@ class Application : android.app.Application() { torchManager = TorchManager(this) } + appWidgetHost = AppWidgetHost(this.applicationContext, APP_WIDGET_HOST_ID) + appWidgetManager = AppWidgetManager.getInstance(this.applicationContext) + + appWidgetHost.startListening() + + val preferences = PreferenceManager.getDefaultSharedPreferences(this) LauncherPreferences.init(preferences, this.resources) - // Try to restore old preferences migratePreferencesToNewVersion(this) @@ -157,4 +170,10 @@ class Application : android.app.Application() { apps.postValue(getApps(packageManager, applicationContext)) } } + + override fun onTerminate() { + appWidgetHost.stopListening() + super.onTerminate() + + } } diff --git a/app/src/main/java/de/jrpie/android/launcher/Functions.kt b/app/src/main/java/de/jrpie/android/launcher/Functions.kt index afc2c31..9679ae5 100644 --- a/app/src/main/java/de/jrpie/android/launcher/Functions.kt +++ b/app/src/main/java/de/jrpie/android/launcher/Functions.kt @@ -6,6 +6,9 @@ import android.app.role.RoleManager import android.content.ActivityNotFoundException import android.content.ClipData import android.content.ClipboardManager +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.appwidget.AppWidgetProviderInfo import android.content.Context import android.content.Intent import android.content.pm.LauncherApps @@ -223,4 +226,4 @@ fun copyToClipboard(context: Context, text: String) { val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipData = ClipData.newPlainText("Debug Info", text) clipboardManager.setPrimaryClip(clipData) -} \ No newline at end of file +} diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java b/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java index 4653910..17624fc 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java @@ -8,6 +8,7 @@ import de.jrpie.android.launcher.actions.lock.LockMethod; import de.jrpie.android.launcher.preferences.serialization.MapAbstractAppInfoStringPreferenceSerializer; import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer; import de.jrpie.android.launcher.preferences.serialization.SetPinnedShortcutInfoPreferenceSerializer; +import de.jrpie.android.launcher.preferences.serialization.SetWidgetInfoSerializer; import de.jrpie.android.launcher.preferences.theme.Background; import de.jrpie.android.launcher.preferences.theme.ColorTheme; import de.jrpie.android.launcher.preferences.theme.Font; @@ -26,6 +27,7 @@ import eu.jonahbauer.android.preference.annotations.Preferences; @Preference(name = "started_time", type = long.class), // see PREFERENCE_VERSION in de.jrpie.android.launcher.preferences.Preferences.kt @Preference(name = "version_code", type = int.class, defaultValue = "-1"), + @Preference(name = "widgets", type = Set.class, serializer = SetWidgetInfoSerializer.class) }), @PreferenceGroup(name = "apps", prefix = "settings_apps_", suffix = "_key", value = { @Preference(name = "favorites", type = Set.class, serializer = SetAbstractAppInfoPreferenceSerializer.class), diff --git a/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt b/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt index 3e19daf..1746e8a 100644 --- a/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt +++ b/app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt @@ -4,6 +4,7 @@ package de.jrpie.android.launcher.preferences.serialization import de.jrpie.android.launcher.apps.AbstractAppInfo import de.jrpie.android.launcher.apps.PinnedShortcutInfo +import de.jrpie.android.launcher.widgets.WidgetInfo import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer import kotlinx.serialization.Serializable @@ -28,6 +29,24 @@ class SetAbstractAppInfoPreferenceSerializer : } } + +@Suppress("UNCHECKED_CAST") +class SetWidgetInfoSerializer : + PreferenceSerializer?, java.util.Set?> { + @Throws(PreferenceSerializationException::class) + override fun serialize(value: java.util.Set?): java.util.Set { + return value?.map(WidgetInfo::serialize) + ?.toHashSet() as java.util.Set + } + + @Throws(PreferenceSerializationException::class) + override fun deserialize(value: java.util.Set?): java.util.Set? { + return value?.map(java.lang.String::toString)?.map(WidgetInfo::deserialize) + ?.toHashSet() as? java.util.Set + } +} + + @Suppress("UNCHECKED_CAST") class SetPinnedShortcutInfoPreferenceSerializer : PreferenceSerializer?, java.util.Set?> { diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt b/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt index 2ab5d9f..c7c017f 100644 --- a/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt +++ b/app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt @@ -9,9 +9,12 @@ import android.os.Bundle import android.view.KeyEvent import android.view.MotionEvent import android.view.View +import android.view.ViewGroup import android.window.OnBackInvokedDispatcher import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible +import androidx.core.view.marginTop +import de.jrpie.android.launcher.Application import de.jrpie.android.launcher.R import de.jrpie.android.launcher.actions.Action import de.jrpie.android.launcher.actions.Gesture @@ -20,7 +23,13 @@ import de.jrpie.android.launcher.databinding.HomeBinding import de.jrpie.android.launcher.openTutorial import de.jrpie.android.launcher.preferences.LauncherPreferences import de.jrpie.android.launcher.ui.tutorial.TutorialActivity +import de.jrpie.android.launcher.widgets.bindAppWidget +import de.jrpie.android.launcher.widgets.createAppWidgetView +import de.jrpie.android.launcher.widgets.deleteAllWidgets +import de.jrpie.android.launcher.widgets.getAppWidgetProviders import java.util.Locale +import kotlin.math.absoluteValue +import kotlin.random.Random /** * [HomeActivity] is the actual application Launcher, @@ -73,6 +82,17 @@ class HomeActivity : UIObject, AppCompatActivity() { binding.buttonFallbackSettings.setOnClickListener { LauncherAction.SETTINGS.invoke(this) } + + deleteAllWidgets(this) + + LauncherPreferences.internal().widgets().forEach { widget -> + createAppWidgetView(this, widget)?.let { + binding.homeWidgetContainer.addView(it) + } + } + + // TODO: appWidgetHost.deleteAppWidgetId(appWidgetId) + } override fun onConfigurationChanged(newConfig: Configuration) { diff --git a/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetContainerView.kt b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetContainerView.kt new file mode 100644 index 0000000..47a1480 --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/ui/widgets/WidgetContainerView.kt @@ -0,0 +1,12 @@ +package de.jrpie.android.launcher.ui.widgets + +import android.content.Context +import android.util.AttributeSet +import android.widget.LinearLayout + +// TODO: implement layout logic instead of linear layout +class WidgetContainerView(context: Context, attrs: AttributeSet?): LinearLayout(context, attrs) { + init { + orientation = VERTICAL + } +} \ No newline at end of file diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetInfo.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetInfo.kt new file mode 100644 index 0000000..c3d9046 --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/WidgetInfo.kt @@ -0,0 +1,28 @@ +package de.jrpie.android.launcher.widgets; + +import de.jrpie.android.launcher.apps.AbstractAppInfo +import kotlinx.serialization.SerialName; +import kotlinx.serialization.Serializable; +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +@Serializable +@SerialName("widget") +class WidgetInfo(val id: Int, val width: Int, val height: Int) { + fun serialize(): String { + return Json.encodeToString(this) + } + + override fun hashCode(): Int { + return id + } + + override fun equals(other: Any?): Boolean { + return (other as? WidgetInfo)?.id == id + } + companion object { + fun deserialize(serialized: String): WidgetInfo { + return Json.decodeFromString(serialized) + } + } +} diff --git a/app/src/main/java/de/jrpie/android/launcher/widgets/Widgets.kt b/app/src/main/java/de/jrpie/android/launcher/widgets/Widgets.kt new file mode 100644 index 0000000..ab4f40d --- /dev/null +++ b/app/src/main/java/de/jrpie/android/launcher/widgets/Widgets.kt @@ -0,0 +1,123 @@ +package de.jrpie.android.launcher.widgets + +import android.app.Activity +import android.app.Service +import android.appwidget.AppWidgetHostView +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProviderInfo +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.os.UserManager +import android.util.Log +import de.jrpie.android.launcher.Application +import de.jrpie.android.launcher.preferences.LauncherPreferences +import kotlin.math.absoluteValue +import kotlin.random.Random + +fun deleteAllWidgets(activity: Activity) { + val appWidgetHost = (activity.application as Application).appWidgetHost + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + appWidgetHost.appWidgetIds.forEach { deleteAppWidget(activity, WidgetInfo(it, 0,0)) } + } +} + +fun bindAppWidget(activity: Activity, providerInfo: AppWidgetProviderInfo): WidgetInfo? { + val appWidgetHost = (activity.application as Application).appWidgetHost + val appWidgetManager = (activity.application as Application).appWidgetManager + val appWidgetId = appWidgetHost.allocateAppWidgetId() + + Log.i("Launcher", "Binding new widget ${appWidgetId}") + if (!appWidgetManager.bindAppWidgetIdIfAllowed( + appWidgetId, + providerInfo.provider + ) + ) { + requestAppWidgetPermission(activity, appWidgetId, providerInfo) + return null + } + try { + Log.e("widgets", "configure widget") + appWidgetHost.startAppWidgetConfigureActivityForResult(activity, appWidgetId, 0, 1, null) + } catch (e: Exception) { + e.printStackTrace() + } + + val widget = WidgetInfo(appWidgetId, 500, 500) + LauncherPreferences.internal().widgets( + (LauncherPreferences.internal().widgets() ?: HashSet()).also { + it.add(widget) + } + ) + + + return widget +} + +fun deleteAppWidget(activity: Activity, widget: WidgetInfo) { + Log.i("Launcher", "Deleting widget ${widget.id}") + val appWidgetHost = (activity.application as Application).appWidgetHost + + appWidgetHost.deleteAppWidgetId(widget.id) + + LauncherPreferences.internal().widgets( + LauncherPreferences.internal().widgets()?.also { + it.remove(widget) + } + ) +} + +fun createAppWidgetView(activity: Activity, widget: WidgetInfo): AppWidgetHostView? { + val appWidgetHost = (activity.application as Application).appWidgetHost + val appWidgetManager = (activity.application as Application).appWidgetManager + val providerInfo = appWidgetManager.getAppWidgetInfo(widget.id) ?: return null + val view = appWidgetHost.createView(activity, widget.id, providerInfo) + .apply { + setAppWidget(appWidgetId, appWidgetInfo) + } + + + val newOptions = Bundle().apply { + putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, widget.width) + putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, widget.width) + putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, widget.height) + putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, widget.height) + } + appWidgetManager.updateAppWidgetOptions( + widget.id, + newOptions + ) + //view.minimumWidth = widget.width + //view.minimumHeight = widget.height + + return view +} + +fun getAppWidgetProviders(context: Context): List { + return appWidgetProviders(context, (context.applicationContext as Application).appWidgetManager) +} + +fun requestAppWidgetPermission(context: Activity, widgetId: Int, info: AppWidgetProviderInfo) { + val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_BIND).apply { + putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId) + putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.provider) + } + context.startActivityForResult(intent, 0)//REQUEST_CODE_BIND_WIDGET) +} + +fun appWidgetProviders( + context: Context, + appWidgetManager: AppWidgetManager +): List { + val userManager = context.getSystemService(Service.USER_SERVICE) as UserManager + return userManager.userProfiles.map { + appWidgetManager.getInstalledProvidersForProfile(it) + }.flatten() +} +fun Activity.bindRandomWidget() { + val selectedWidget = + getAppWidgetProviders(this).let { it.get(Random.nextInt().absoluteValue % it.size) } + bindAppWidget(this, selectedWidget) ?: return +} + diff --git a/app/src/main/res/layout/home.xml b/app/src/main/res/layout/home.xml index ecefdea..444bd4e 100644 --- a/app/src/main/res/layout/home.xml +++ b/app/src/main/res/layout/home.xml @@ -9,6 +9,15 @@ android:longClickable="false" android:fitsSystemWindows="true" tools:context=".ui.HomeActivity"> + + + internal.started_before internal.first_startup internal.version_code + internal.widgets apps.favorites apps.hidden apps.pinned_shortcuts