Skip to content

Commit 7d3f2e9

Browse files
anikikilandomen
andauthored
[Android] Take 2 on this issue: The latest Android 16 update broke the update for the Search and Favorites widget (#6885)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1202552961248957/task/1211543179581893 ### Description This PR brings back the initial changes made as part of https://app.asana.com/1/137249556945/project/1202552961248957/task/1211451449729774 and fixes the issue for old OS versions. ### Steps to test this PR Test the Search and Favorites widget on OS versions like: 8, 9 , 10, 11, 12 --------- Co-authored-by: Domen Lanišnik <landomen@users.noreply.github.com>
1 parent cc302d4 commit 7d3f2e9

30 files changed

+511
-1132
lines changed

app/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,9 @@ dependencies {
425425
implementation project(':serp-logos-api')
426426
implementation project(':serp-logos-impl')
427427

428+
// Widgets
429+
implementation "androidx.core:core-remoteviews:1.1.0"
430+
428431
// Deprecated. TODO: Stop using this artifact.
429432
implementation "androidx.legacy:legacy-support-v4:_"
430433
debugImplementation Square.leakCanary.android

app/src/main/AndroidManifest.xml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -488,16 +488,6 @@
488488
android:parentActivityName="com.duckduckgo.app.settings.SettingsActivity"
489489
/>
490490

491-
<service
492-
android:name="com.duckduckgo.widget.FavoritesWidgetService"
493-
android:exported="false"
494-
android:permission="android.permission.BIND_REMOTEVIEWS" />
495-
496-
<service
497-
android:name="com.duckduckgo.widget.EmptyFavoritesWidgetService"
498-
android:exported="false"
499-
android:permission="android.permission.BIND_REMOTEVIEWS" />
500-
501491
<service
502492
android:name="com.duckduckgo.customtabs.impl.service.DuckDuckGoCustomTabService"
503493
android:exported="true"

app/src/main/java/com/duckduckgo/app/di/AppComponent.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ import com.duckduckgo.app.onboarding.di.OnboardingModule
2727
import com.duckduckgo.app.surrogates.di.ResourceSurrogateModule
2828
import com.duckduckgo.app.usage.di.AppUsageModule
2929
import com.duckduckgo.di.scopes.AppScope
30-
import com.duckduckgo.widget.EmptyFavoritesWidgetService
31-
import com.duckduckgo.widget.FavoritesWidgetService
30+
import com.duckduckgo.widget.EmptyFavoritesWidgetItemFactory
31+
import com.duckduckgo.widget.FavoritesWidgetItemFactory
3232
import com.duckduckgo.widget.SearchAndFavoritesWidget
3333
import com.duckduckgo.widget.SearchOnlyWidget
3434
import com.duckduckgo.widget.SearchWidget
@@ -88,11 +88,11 @@ interface AppComponent : AndroidInjector<DuckDuckGoApplication> {
8888

8989
fun inject(searchOnlyWidget: SearchOnlyWidget)
9090

91-
fun inject(searchAndFavsWidget: SearchAndFavoritesWidget)
91+
fun inject(searchAndFavoritesWidget: SearchAndFavoritesWidget)
9292

93-
fun inject(favoritesWidgetItemFactory: FavoritesWidgetService.FavoritesWidgetItemFactory)
93+
fun inject(favoritesWidgetItemFactory: FavoritesWidgetItemFactory)
9494

95-
fun inject(emptyFavoritesWidgetItemFactory: EmptyFavoritesWidgetService.EmptyFavoritesWidgetItemFactory)
95+
fun inject(emptyFavoritesWidgetItemFactory: EmptyFavoritesWidgetItemFactory)
9696

9797
// accessor to Retrofit instance for test only only for test
9898
@Named("api")

app/src/main/java/com/duckduckgo/app/widget/FavoritesObserver.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ package com.duckduckgo.app.widget
1919
import android.appwidget.AppWidgetManager
2020
import android.content.ComponentName
2121
import android.content.Context
22+
import android.content.Intent
2223
import androidx.lifecycle.LifecycleOwner
2324
import androidx.lifecycle.coroutineScope
24-
import com.duckduckgo.app.browser.R
2525
import com.duckduckgo.app.lifecycle.MainProcessLifecycleObserver
2626
import com.duckduckgo.common.utils.DispatcherProvider
2727
import com.duckduckgo.di.scopes.AppScope
@@ -33,7 +33,7 @@ import javax.inject.Inject
3333

3434
@SingleInstanceIn(AppScope::class)
3535
class FavoritesObserver @Inject constructor(
36-
context: Context,
36+
private val context: Context,
3737
private val savedSitesRepository: SavedSitesRepository,
3838
private val dispatcherProvider: DispatcherProvider,
3939
) : MainProcessLifecycleObserver {
@@ -47,8 +47,13 @@ class FavoritesObserver @Inject constructor(
4747
owner.lifecycle.coroutineScope.launch(dispatcherProvider.io()) {
4848
appWidgetManager?.let { instance ->
4949
savedSitesRepository.getFavorites().collect {
50-
instance.notifyAppWidgetViewDataChanged(instance.getAppWidgetIds(componentName), R.id.favoritesGrid)
51-
instance.notifyAppWidgetViewDataChanged(instance.getAppWidgetIds(componentName), R.id.emptyfavoritesGrid)
50+
val appWidgetIds = instance.getAppWidgetIds(componentName)
51+
if (appWidgetIds.isNotEmpty()) {
52+
val updateIntent = Intent(context, SearchAndFavoritesWidget::class.java)
53+
updateIntent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
54+
updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds)
55+
context.sendBroadcast(updateIntent)
56+
}
5257
}
5358
}
5459
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright (c) 2021 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.widget
18+
19+
import android.content.Context
20+
import android.widget.RemoteViews
21+
import android.widget.RemoteViewsService
22+
import com.duckduckgo.app.browser.R
23+
import com.duckduckgo.app.global.DuckDuckGoApplication
24+
import com.duckduckgo.common.utils.DispatcherProvider
25+
import com.duckduckgo.savedsites.api.SavedSitesRepository
26+
import kotlinx.coroutines.withContext
27+
import logcat.logcat
28+
import javax.inject.Inject
29+
30+
/**
31+
* This RemoteViewsFactory will not render any item. It's used for convenience to simplify executing background operations to show/hide empty widget CTA.
32+
* If this RemoteViewsFactory count is 0, SearchAndFavoritesWidget R.id.emptyFavoritesGrid will show the configured EmptyView.
33+
*/
34+
class EmptyFavoritesWidgetItemFactory(
35+
val context: Context,
36+
) : RemoteViewsService.RemoteViewsFactory {
37+
38+
@Inject
39+
lateinit var savedSitesRepository: SavedSitesRepository
40+
41+
@Inject
42+
lateinit var dispatchers: DispatcherProvider
43+
44+
private var count = 0
45+
46+
override fun onCreate() {
47+
inject(context)
48+
}
49+
50+
override fun onDataSetChanged() {
51+
// no-op, we use our own update mechanism
52+
}
53+
54+
suspend fun updateEmptyWidgetFavoritesAsync() {
55+
runCatching {
56+
count = getItemsCountFromFavorites()
57+
}.onFailure { error ->
58+
logcat { "Failed to update Search and Favorites widget when empty: ${error.message}" }
59+
}
60+
}
61+
62+
override fun onDestroy() {
63+
// no-op
64+
}
65+
66+
override fun getCount(): Int {
67+
return count
68+
}
69+
70+
override fun getViewAt(position: Int): RemoteViews {
71+
return RemoteViews(context.packageName, R.layout.empty_view)
72+
}
73+
74+
override fun getLoadingView(): RemoteViews {
75+
return RemoteViews(context.packageName, R.layout.empty_view)
76+
}
77+
78+
override fun getViewTypeCount(): Int {
79+
return 1
80+
}
81+
82+
override fun getItemId(position: Int): Long {
83+
return position.toLong()
84+
}
85+
86+
override fun hasStableIds(): Boolean {
87+
return true
88+
}
89+
90+
private suspend fun getItemsCountFromFavorites(): Int {
91+
return withContext(dispatchers.io()) {
92+
if (savedSitesRepository.hasFavorites()) 1 else 0
93+
}
94+
}
95+
96+
private fun inject(context: Context) {
97+
val application = context.applicationContext as DuckDuckGoApplication
98+
application.daggerAppComponent.inject(this)
99+
}
100+
}

app/src/main/java/com/duckduckgo/widget/EmptyFavoritesWidgetService.kt

Lines changed: 0 additions & 92 deletions
This file was deleted.

0 commit comments

Comments
 (0)