Skip to content

Commit e7a06c4

Browse files
committed
add support for app widgets (see #44)
1 parent 077bd1c commit e7a06c4

35 files changed

+1691
-100
lines changed

app/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ dependencies {
106106
implementation 'com.google.android.material:material:1.12.0'
107107
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
108108
implementation "eu.jonahbauer:android-preference-annotations:1.1.2"
109+
implementation 'androidx.activity:activity:1.10.1'
109110
annotationProcessor "eu.jonahbauer:android-preference-annotations:1.1.2"
110111
annotationProcessor "com.android.databinding:compiler:$android_plugin_version"
111112
testImplementation 'junit:junit:4.13.2'

app/src/main/AndroidManifest.xml

+9-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
tools:ignore="QueryAllPackagesPermission" />
99
<uses-permission android:name="android.permission.ACCESS_HIDDEN_PROFILES" />
1010
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
11+
<uses-permission android:name="android.permission.BIND_APPWIDGET" />
1112

1213
<application
1314
android:name=".Application"
@@ -19,6 +20,13 @@
1920
android:supportsRtl="true"
2021
android:theme="@style/launcherBaseTheme"
2122
tools:ignore="UnusedAttribute">
23+
<activity
24+
android:name=".ui.widgets.manage.ManageWidgetsActivity"
25+
android:theme="@style/launcherHomeTheme"
26+
android:exported="false" />
27+
<activity
28+
android:name=".ui.widgets.manage.SelectWidgetActivity"
29+
android:exported="false" />
2230
<activity
2331
android:name=".ui.PinShortcutActivity"
2432
android:autoRemoveFromRecents="true"
@@ -97,4 +105,4 @@
97105
</service>
98106
</application>
99107

100-
</manifest>
108+
</manifest>

app/src/main/java/de/jrpie/android/launcher/Application.kt

+25-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import android.os.Build.VERSION_CODES
1212
import android.os.UserHandle
1313
import androidx.core.content.ContextCompat
1414
import androidx.lifecycle.MutableLiveData
15+
import android.appwidget.AppWidgetHost
16+
import android.appwidget.AppWidgetManager
1517
import androidx.preference.PreferenceManager
1618
import de.jrpie.android.launcher.actions.TorchManager
1719
import de.jrpie.android.launcher.apps.AbstractAppInfo
@@ -20,13 +22,22 @@ import de.jrpie.android.launcher.apps.isPrivateSpaceLocked
2022
import de.jrpie.android.launcher.preferences.LauncherPreferences
2123
import de.jrpie.android.launcher.preferences.migratePreferencesToNewVersion
2224
import de.jrpie.android.launcher.preferences.resetPreferences
25+
import de.jrpie.android.launcher.widgets.LauncherWidgetProvider
26+
import de.jrpie.android.launcher.widgets.Widget
2327
import kotlinx.coroutines.CoroutineScope
2428
import kotlinx.coroutines.Dispatchers
2529
import kotlinx.coroutines.launch
2630

31+
32+
const val APP_WIDGET_HOST_ID = 42;
33+
34+
2735
class Application : android.app.Application() {
2836
val apps = MutableLiveData<List<AbstractDetailedAppInfo>>()
37+
val widgets = MutableLiveData<Set<Widget>>()
2938
val privateSpaceLocked = MutableLiveData<Boolean>()
39+
lateinit var appWidgetHost: AppWidgetHost
40+
lateinit var appWidgetManager: AppWidgetManager
3041

3142
private val profileAvailabilityBroadcastReceiver = object : BroadcastReceiver() {
3243
override fun onReceive(context: Context?, intent: Intent?) {
@@ -90,6 +101,8 @@ class Application : android.app.Application() {
90101
customAppNames = LauncherPreferences.apps().customNames()
91102
} else if (pref == LauncherPreferences.apps().keys().pinnedShortcuts()) {
92103
loadApps()
104+
} else if (pref == LauncherPreferences.widgets().keys().widgets()) {
105+
widgets.postValue(LauncherPreferences.widgets().widgets() ?: setOf())
93106
}
94107
}
95108

@@ -103,10 +116,15 @@ class Application : android.app.Application() {
103116
torchManager = TorchManager(this)
104117
}
105118

119+
appWidgetHost = AppWidgetHost(this.applicationContext, APP_WIDGET_HOST_ID)
120+
appWidgetManager = AppWidgetManager.getInstance(this.applicationContext)
121+
122+
appWidgetHost.startListening()
123+
124+
106125
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
107126
LauncherPreferences.init(preferences, this.resources)
108127

109-
110128
// Try to restore old preferences
111129
migratePreferencesToNewVersion(this)
112130

@@ -157,4 +175,10 @@ class Application : android.app.Application() {
157175
apps.postValue(getApps(packageManager, applicationContext))
158176
}
159177
}
178+
179+
override fun onTerminate() {
180+
appWidgetHost.stopListening()
181+
super.onTerminate()
182+
183+
}
160184
}

app/src/main/java/de/jrpie/android/launcher/Functions.kt

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import android.app.role.RoleManager
66
import android.content.ActivityNotFoundException
77
import android.content.ClipData
88
import android.content.ClipboardManager
9+
import android.appwidget.AppWidgetManager
10+
import android.appwidget.AppWidgetProvider
11+
import android.appwidget.AppWidgetProviderInfo
912
import android.content.Context
1013
import android.content.Intent
1114
import android.content.pm.LauncherApps
@@ -223,4 +226,4 @@ fun copyToClipboard(context: Context, text: String) {
223226
val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
224227
val clipData = ClipData.newPlainText("Debug Info", text)
225228
clipboardManager.setPrimaryClip(clipData)
226-
}
229+
}

app/src/main/java/de/jrpie/android/launcher/preferences/LauncherPreferences$Config.java

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import de.jrpie.android.launcher.preferences.serialization.MapAbstractAppInfoStringPreferenceSerializer;
99
import de.jrpie.android.launcher.preferences.serialization.SetAbstractAppInfoPreferenceSerializer;
1010
import de.jrpie.android.launcher.preferences.serialization.SetPinnedShortcutInfoPreferenceSerializer;
11+
import de.jrpie.android.launcher.preferences.serialization.SetWidgetSerializer;
1112
import de.jrpie.android.launcher.preferences.theme.Background;
1213
import de.jrpie.android.launcher.preferences.theme.ColorTheme;
1314
import de.jrpie.android.launcher.preferences.theme.Font;
@@ -82,5 +83,8 @@
8283
@PreferenceGroup(name = "actions", prefix = "settings_actions_", suffix = "_key", value = {
8384
@Preference(name = "lock_method", type = LockMethod.class, defaultValue = "DEVICE_ADMIN"),
8485
}),
86+
@PreferenceGroup(name = "widgets", prefix = "settings_widgets_", suffix= "_key", value = {
87+
@Preference(name = "widgets", type = Set.class, serializer = SetWidgetSerializer.class)
88+
}),
8589
})
8690
public final class LauncherPreferences$Config {}

app/src/main/java/de/jrpie/android/launcher/preferences/Preferences.kt

+10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersio
1313
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersion3
1414
import de.jrpie.android.launcher.preferences.legacy.migratePreferencesFromVersionUnknown
1515
import de.jrpie.android.launcher.ui.HomeActivity
16+
import de.jrpie.android.launcher.widgets.ClockWidget
17+
import de.jrpie.android.launcher.widgets.WidgetPosition
18+
import de.jrpie.android.launcher.widgets.deleteAllWidgets
1619

1720
/* Current version of the structure of preferences.
1821
* Increase when breaking changes are introduced and write an appropriate case in
@@ -71,6 +74,13 @@ fun resetPreferences(context: Context) {
7174
Log.i(TAG, "Resetting preferences")
7275
LauncherPreferences.clear()
7376
LauncherPreferences.internal().versionCode(PREFERENCE_VERSION)
77+
deleteAllWidgets(context)
78+
79+
LauncherPreferences.widgets().widgets(
80+
setOf(
81+
ClockWidget(-500, WidgetPosition(1,4,10,3))
82+
)
83+
)
7484

7585

7686
val hidden: MutableSet<AbstractAppInfo> = mutableSetOf()

app/src/main/java/de/jrpie/android/launcher/preferences/serialization/PreferenceSerializers.kt

+19
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package de.jrpie.android.launcher.preferences.serialization
44

55
import de.jrpie.android.launcher.apps.AbstractAppInfo
66
import de.jrpie.android.launcher.apps.PinnedShortcutInfo
7+
import de.jrpie.android.launcher.widgets.Widget
78
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializationException
89
import eu.jonahbauer.android.preference.annotations.serializer.PreferenceSerializer
910
import kotlinx.serialization.Serializable
@@ -28,6 +29,24 @@ class SetAbstractAppInfoPreferenceSerializer :
2829
}
2930
}
3031

32+
33+
@Suppress("UNCHECKED_CAST")
34+
class SetWidgetSerializer :
35+
PreferenceSerializer<java.util.Set<Widget>?, java.util.Set<java.lang.String>?> {
36+
@Throws(PreferenceSerializationException::class)
37+
override fun serialize(value: java.util.Set<Widget>?): java.util.Set<java.lang.String>? {
38+
return value?.map(Widget::serialize)
39+
?.toHashSet() as? java.util.Set<java.lang.String>
40+
}
41+
42+
@Throws(PreferenceSerializationException::class)
43+
override fun deserialize(value: java.util.Set<java.lang.String>?): java.util.Set<Widget>? {
44+
return value?.map(java.lang.String::toString)?.map(Widget::deserialize)
45+
?.toHashSet() as? java.util.Set<Widget>
46+
}
47+
}
48+
49+
3150
@Suppress("UNCHECKED_CAST")
3251
class SetPinnedShortcutInfoPreferenceSerializer :
3352
PreferenceSerializer<java.util.Set<PinnedShortcutInfo>?, java.util.Set<java.lang.String>?> {

app/src/main/java/de/jrpie/android/launcher/ui/HomeActivity.kt

+25-70
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package de.jrpie.android.launcher.ui
22

33
import android.annotation.SuppressLint
4+
import android.app.Activity
45
import android.content.SharedPreferences
56
import android.content.res.Configuration
67
import android.content.res.Resources
@@ -10,8 +11,7 @@ import android.view.KeyEvent
1011
import android.view.MotionEvent
1112
import android.view.View
1213
import android.window.OnBackInvokedDispatcher
13-
import androidx.appcompat.app.AppCompatActivity
14-
import androidx.core.view.isVisible
14+
import de.jrpie.android.launcher.Application
1515
import de.jrpie.android.launcher.R
1616
import de.jrpie.android.launcher.actions.Action
1717
import de.jrpie.android.launcher.actions.Gesture
@@ -20,7 +20,6 @@ import de.jrpie.android.launcher.databinding.HomeBinding
2020
import de.jrpie.android.launcher.openTutorial
2121
import de.jrpie.android.launcher.preferences.LauncherPreferences
2222
import de.jrpie.android.launcher.ui.tutorial.TutorialActivity
23-
import java.util.Locale
2423

2524
/**
2625
* [HomeActivity] is the actual application Launcher,
@@ -34,7 +33,7 @@ import java.util.Locale
3433
* - Setting global variables (preferences etc.)
3534
* - Opening the [TutorialActivity] on new installations
3635
*/
37-
class HomeActivity : UIObject, AppCompatActivity() {
36+
class HomeActivity : UIObject, Activity() {
3837

3938
private lateinit var binding: HomeBinding
4039
private var touchGestureDetector: TouchGestureDetector? = null
@@ -45,15 +44,18 @@ class HomeActivity : UIObject, AppCompatActivity() {
4544
prefKey?.startsWith("display.") == true
4645
) {
4746
recreate()
48-
}
49-
50-
if (prefKey?.startsWith("action.") == true) {
47+
} else if (prefKey?.startsWith("action.") == true) {
5148
updateSettingsFallbackButtonVisibility()
49+
} else if (prefKey == LauncherPreferences.widgets().keys().widgets()) {
50+
binding.homeWidgetContainer.updateWidgets(this@HomeActivity,
51+
LauncherPreferences.widgets().widgets()
52+
)
5253
}
54+
5355
}
5456

5557
override fun onCreate(savedInstanceState: Bundle?) {
56-
super<AppCompatActivity>.onCreate(savedInstanceState)
58+
super<Activity>.onCreate(savedInstanceState)
5759
super<UIObject>.onCreate()
5860

5961

@@ -81,8 +83,7 @@ class HomeActivity : UIObject, AppCompatActivity() {
8183
}
8284

8385
override fun onStart() {
84-
super<AppCompatActivity>.onStart()
85-
86+
super<Activity>.onStart()
8687
super<UIObject>.onStart()
8788

8889
// If the tutorial was not finished, start it
@@ -93,6 +94,15 @@ class HomeActivity : UIObject, AppCompatActivity() {
9394
LauncherPreferences.getSharedPreferences()
9495
.registerOnSharedPreferenceChangeListener(sharedPreferencesListener)
9596

97+
(application as Application).appWidgetHost.startListening()
98+
99+
}
100+
101+
102+
103+
override fun onStop() {
104+
(application as Application).appWidgetHost.stopListening()
105+
super.onStop()
96106
}
97107

98108
override fun onWindowFocusChanged(hasFocus: Boolean) {
@@ -118,44 +128,6 @@ class HomeActivity : UIObject, AppCompatActivity() {
118128
}
119129
}
120130

121-
private fun initClock() {
122-
val locale = Locale.getDefault()
123-
val dateVisible = LauncherPreferences.clock().dateVisible()
124-
val timeVisible = LauncherPreferences.clock().timeVisible()
125-
126-
var dateFMT = "yyyy-MM-dd"
127-
var timeFMT = "HH:mm"
128-
if (LauncherPreferences.clock().showSeconds()) {
129-
timeFMT += ":ss"
130-
}
131-
132-
if (LauncherPreferences.clock().localized()) {
133-
dateFMT = android.text.format.DateFormat.getBestDateTimePattern(locale, dateFMT)
134-
timeFMT = android.text.format.DateFormat.getBestDateTimePattern(locale, timeFMT)
135-
}
136-
137-
var upperFormat = dateFMT
138-
var lowerFormat = timeFMT
139-
var upperVisible = dateVisible
140-
var lowerVisible = timeVisible
141-
142-
if (LauncherPreferences.clock().flipDateTime()) {
143-
upperFormat = lowerFormat.also { lowerFormat = upperFormat }
144-
upperVisible = lowerVisible.also { lowerVisible = upperVisible }
145-
}
146-
147-
binding.homeUpperView.isVisible = upperVisible
148-
binding.homeLowerView.isVisible = lowerVisible
149-
150-
binding.homeUpperView.setTextColor(LauncherPreferences.clock().color())
151-
binding.homeLowerView.setTextColor(LauncherPreferences.clock().color())
152-
153-
binding.homeLowerView.format24Hour = lowerFormat
154-
binding.homeUpperView.format24Hour = upperFormat
155-
binding.homeLowerView.format12Hour = lowerFormat
156-
binding.homeUpperView.format12Hour = upperFormat
157-
}
158-
159131
override fun getTheme(): Resources.Theme {
160132
val mTheme = modifyTheme(super.getTheme())
161133
mTheme.applyStyle(R.style.backgroundWallpaper, true)
@@ -193,9 +165,11 @@ class HomeActivity : UIObject, AppCompatActivity() {
193165
windowInsets
194166
}
195167
}
196-
197-
initClock()
198168
updateSettingsFallbackButtonVisibility()
169+
170+
binding.homeWidgetContainer.updateWidgets(this@HomeActivity,
171+
LauncherPreferences.widgets().widgets()
172+
)
199173
}
200174

201175
override fun onDestroy() {
@@ -233,30 +207,11 @@ class HomeActivity : UIObject, AppCompatActivity() {
233207
}
234208

235209
override fun onTouchEvent(event: MotionEvent): Boolean {
210+
android.util.Log.e("Launcher", "on touch")
236211
touchGestureDetector?.onTouchEvent(event)
237212
return true
238213
}
239214

240-
override fun setOnClicks() {
241-
242-
binding.homeUpperView.setOnClickListener {
243-
if (LauncherPreferences.clock().flipDateTime()) {
244-
Gesture.TIME(this)
245-
} else {
246-
Gesture.DATE(this)
247-
}
248-
}
249-
250-
binding.homeLowerView.setOnClickListener {
251-
if (LauncherPreferences.clock().flipDateTime()) {
252-
Gesture.DATE(this)
253-
} else {
254-
Gesture.TIME(this)
255-
}
256-
}
257-
}
258-
259-
260215
private fun handleBack() {
261216
Gesture.BACK(this)
262217
}

app/src/main/java/de/jrpie/android/launcher/ui/PinShortcutActivity.kt

+15-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,21 @@ class PinShortcutActivity : AppCompatActivity(), UIObject {
4949

5050
val request = launcherApps.getPinItemRequest(intent)
5151
this.request = request
52-
if (request == null || request.requestType != PinItemRequest.REQUEST_TYPE_SHORTCUT) {
52+
if (request == null) {
53+
finish()
54+
return
55+
}
56+
57+
if (request.requestType == PinItemRequest.REQUEST_TYPE_APPWIDGET) {
58+
59+
// TODO
60+
request.getAppWidgetProviderInfo(this)
61+
// startActivity()
62+
finish()
63+
return
64+
}
65+
66+
if (request.requestType != PinItemRequest.REQUEST_TYPE_SHORTCUT) {
5367
finish()
5468
return
5569
}

0 commit comments

Comments
 (0)