some tests

This commit is contained in:
Josia Pietsch 2024-10-01 19:37:12 +02:00
parent 077bd1ce44
commit 49ae7e130f
Signed by: jrpie
GPG key ID: E70B571D66986A2D
11 changed files with 239 additions and 2 deletions

View file

@ -8,6 +8,7 @@
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.ACCESS_HIDDEN_PROFILES" />
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
<!--<uses-permission android:name="android.permission.BIND_APPWIDGET" />-->
<application
android:name=".Application"

View file

@ -12,6 +12,8 @@ import android.os.Build.VERSION_CODES
import android.os.UserHandle
import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData
import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import androidx.preference.PreferenceManager
import de.jrpie.android.launcher.actions.TorchManager
import de.jrpie.android.launcher.apps.AbstractAppInfo
@ -24,9 +26,15 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
const val APP_WIDGET_HOST_ID = 42;
class Application : android.app.Application() {
val apps = MutableLiveData<List<AbstractDetailedAppInfo>>()
val privateSpaceLocked = MutableLiveData<Boolean>()
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()
}
}

View file

@ -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)
}
}

View file

@ -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),

View file

@ -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<WidgetInfo>?, java.util.Set<java.lang.String>?> {
@Throws(PreferenceSerializationException::class)
override fun serialize(value: java.util.Set<WidgetInfo>?): java.util.Set<java.lang.String> {
return value?.map(WidgetInfo::serialize)
?.toHashSet() as java.util.Set<java.lang.String>
}
@Throws(PreferenceSerializationException::class)
override fun deserialize(value: java.util.Set<java.lang.String>?): java.util.Set<WidgetInfo>? {
return value?.map(java.lang.String::toString)?.map(WidgetInfo::deserialize)
?.toHashSet() as? java.util.Set<WidgetInfo>
}
}
@Suppress("UNCHECKED_CAST")
class SetPinnedShortcutInfoPreferenceSerializer :
PreferenceSerializer<java.util.Set<PinnedShortcutInfo>?, java.util.Set<java.lang.String>?> {

View file

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

View file

@ -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
}
}

View file

@ -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)
}
}
}

View file

@ -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<AppWidgetProviderInfo> {
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<AppWidgetProviderInfo> {
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
}

View file

@ -9,6 +9,15 @@
android:longClickable="false"
android:fitsSystemWindows="true"
tools:context=".ui.HomeActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent" >
<de.jrpie.android.launcher.ui.widgets.WidgetContainerView
android:id="@+id/home_widget_container"
android:paddingLeft="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
<TextClock
android:id="@+id/home_upper_view"

View file

@ -9,6 +9,7 @@
<string name="settings_internal_started_key" translatable="false">internal.started_before</string>
<string name="settings_internal_started_time_key" translatable="false">internal.first_startup</string>
<string name="settings_internal_version_code_key" translatable="false">internal.version_code</string>
<string name="settings_internal_widgets_key" translatable="false">internal.widgets</string>
<string name="settings_apps_favorites_key" translatable="false">apps.favorites</string>
<string name="settings_apps_hidden_key" translatable="false">apps.hidden</string>
<string name="settings_apps_pinned_shortcuts_key" translatable="false">apps.pinned_shortcuts</string>