Skip to content

Commit b65a744

Browse files
authored
Fix pixels for widgets + additional pixels (#6180)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1200581511062568/task/1210412907075748?focus=true ### Description - Added specific pixel events when search and search & favorites widgets are added or deleted - Created a new SearchWidgetLifecycleDelegate to handle common widget lifecycle logic - Fixed Enter key handling in SystemSearchActivity to properly detect key down events - Added unique request codes for widget PendingIntents to fix tracking. ### Steps to test this PR Check one pixel is sent for simple search widget added and one for deleted - [x] Install from this branch. - [x] Add the simple search widget (light). - [x] Check the `m_search_widget_added` pixel is sent. - [x] Add another simple search widget (light). - [x] Check the `m_search_widget_added` pixel is not sent again. - [x] Delete all widgets. - [x] Check the `m_search_widget_deleted` pixel is sent once. Check one pixel is sent for the search and favorites widget added and one for deleted - [x] Install from this branch. - [x] Add the simple search widget. - [x] Check the `m_search_and_favorites_widget_added` pixel is sent. - [x] Add another simple search widget. - [x] Check the `m_search_and_favorites_widget_added` pixel is not sent again. - [x] Delete all widgets. - [x] Check the `m_search_and_favorites_widget_deleted` pixel is sent once. Check existing functionality: one pixel is sent no matter what widget is added and one for deleted - [x] Install from this branch. - [x] Add the simple search widget or the search and favorites widget. - [x] Check the `m_w_a` pixel is sent. - [x] Add another widget or as many as you want - [x] Check the `m_w_a` pixel is not sent again. - [x] Delete all widgets. - [x] Check the `m_w_d` pixel is sent once. Check existing functionality (search from widget) - [x] Install from this branch. - [x] Add the simple search widget and the search and favorites widget. - [x] Make a search from the simple search widget and check `m_i_lbq` is sent once. - [x] Make a search from the search and favorites widget and check `m_i_lbq` is sent once. Check widget launched - [x] Install from this branch. - [x] Add the simple search widget and the search and favorites widget. - [x] Tap on the simple search widget and check `m_w_l` is sent. - [x] Tap on the search and favorites widget and check `m_sfbw_l` is sent. - [x] Remove all widgets and add them again in a different order: the search and favorites widget and then the simple search widget. - [x] Make a search from the search and favorites widget and check `m_i_lbq` is sent once. - [x] Tap on the simple search widget and check `m_w_l` is sent. - [x] Tap on the search and favorites widget and check `m_sfbw_l` is sent.
1 parent 44f8f44 commit b65a744

File tree

6 files changed

+99
-28
lines changed

6 files changed

+99
-28
lines changed

app/src/main/java/com/duckduckgo/app/global/api/PixelParamRemovalInterceptor.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ object PixelInterceptorPixelsRequiringDataCleaning : PixelParamRemovalPlugin {
110110
AppPixelName.PREONBOARDING_SKIP_ONBOARDING_PRESSED.pixelName to PixelParameter.removeAtb(),
111111
AppPixelName.PREONBOARDING_CONFIRM_SKIP_ONBOARDING_PRESSED.pixelName to PixelParameter.removeAtb(),
112112
AppPixelName.PREONBOARDING_RESUME_ONBOARDING_PRESSED.pixelName to PixelParameter.removeAtb(),
113+
AppPixelName.SEARCH_AND_FAVORITES_WIDGET_ADDED.pixelName to PixelParameter.removeAtb(),
114+
AppPixelName.SEARCH_AND_FAVORITES_WIDGET_DELETED.pixelName to PixelParameter.removeAtb(),
115+
AppPixelName.SEARCH_WIDGET_ADDED.pixelName to PixelParameter.removeAtb(),
116+
AppPixelName.SEARCH_WIDGET_DELETED.pixelName to PixelParameter.removeAtb(),
113117
)
114118
}
115119
}

app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ enum class AppPixelName(override val pixelName: String) : Pixel.PixelName {
7979
FAVORITES_WIDGETS_LIGHT(pixelName = "m_sfw_l"),
8080
FAVORITES_WIDGETS_DARK(pixelName = "m_sfw_dk"),
8181
FAVORITES_WIDGETS_SYSTEM(pixelName = "m_sfw_sd"),
82+
SEARCH_AND_FAVORITES_WIDGET_ADDED(pixelName = "m_search_and_favorites_widget_added"),
83+
SEARCH_AND_FAVORITES_WIDGET_DELETED(pixelName = "m_search_and_favorites_widget_deleted"),
84+
SEARCH_WIDGET_ADDED(pixelName = "m_search_widget_added"),
85+
SEARCH_WIDGET_DELETED(pixelName = "m_search_widget_deleted"),
8286

8387
FAVORITE_OMNIBAR_ITEM_PRESSED("m_fav_o"),
8488
FAVORITE_HOMETAB_ITEM_PRESSED("m_fav_ht"),

app/src/main/java/com/duckduckgo/app/systemsearch/SystemSearchActivity.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,9 @@ class SystemSearchActivity : DuckDuckGoActivity() {
354354
private fun configureTextInput() {
355355
omnibarTextInput.setOnEditorActionListener(
356356
TextView.OnEditorActionListener { _, actionId, keyEvent ->
357-
if (actionId == EditorInfo.IME_ACTION_GO || keyEvent?.keyCode == KeyEvent.KEYCODE_ENTER) {
357+
if ((keyEvent == null && actionId == EditorInfo.IME_ACTION_GO) ||
358+
(keyEvent?.keyCode == KeyEvent.KEYCODE_ENTER && keyEvent.action == KeyEvent.ACTION_DOWN)
359+
) {
358360
viewModel.userSubmittedQuery(omnibarTextInput.text.toString())
359361
return@OnEditorActionListener true
360362
}

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import com.duckduckgo.app.browser.BrowserActivity
3131
import com.duckduckgo.app.browser.R
3232
import com.duckduckgo.app.di.AppCoroutineScope
3333
import com.duckduckgo.app.global.DuckDuckGoApplication
34+
import com.duckduckgo.app.pixels.AppPixelName.SEARCH_AND_FAVORITES_WIDGET_ADDED
35+
import com.duckduckgo.app.pixels.AppPixelName.SEARCH_AND_FAVORITES_WIDGET_DELETED
3436
import com.duckduckgo.app.systemsearch.SystemSearchActivity
3537
import com.duckduckgo.appbuildconfig.api.AppBuildConfig
3638
import com.duckduckgo.common.utils.DispatcherProvider
@@ -77,6 +79,9 @@ class SearchAndFavoritesWidget : AppWidgetProvider() {
7779
@Inject
7880
lateinit var dispatchers: DispatcherProvider
7981

82+
@Inject
83+
lateinit var searchWidgetLifecycleDelegate: SearchWidgetLifecycleDelegate
84+
8085
private var layoutId: Int = R.layout.search_favorites_widget_daynight_auto
8186

8287
override fun onReceive(
@@ -87,6 +92,11 @@ class SearchAndFavoritesWidget : AppWidgetProvider() {
8792
super.onReceive(context, intent)
8893
}
8994

95+
override fun onEnabled(context: Context) {
96+
super.onEnabled(context)
97+
searchWidgetLifecycleDelegate.handleOnWidgetEnabled(SEARCH_AND_FAVORITES_WIDGET_ADDED)
98+
}
99+
90100
override fun onUpdate(
91101
context: Context,
92102
appWidgetManager: AppWidgetManager,
@@ -126,6 +136,11 @@ class SearchAndFavoritesWidget : AppWidgetProvider() {
126136
super.onDeleted(context, appWidgetIds)
127137
}
128138

139+
override fun onDisabled(context: Context?) {
140+
super.onDisabled(context)
141+
searchWidgetLifecycleDelegate.handleOnWidgetDisabled(SEARCH_AND_FAVORITES_WIDGET_DELETED)
142+
}
143+
129144
private suspend fun updateWidget(
130145
context: Context,
131146
appWidgetManager: AppWidgetManager,
@@ -275,11 +290,20 @@ class SearchAndFavoritesWidget : AppWidgetProvider() {
275290

276291
private fun buildPendingIntent(context: Context): PendingIntent {
277292
val intent = SystemSearchActivity.fromFavWidget(context)
278-
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
293+
return PendingIntent.getActivity(
294+
context,
295+
SEARCH_AND_FAVORITES_WIDGET_REQUEST_CODE,
296+
intent,
297+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
298+
)
279299
}
280300

281301
private fun inject(context: Context) {
282302
val application = context.applicationContext as DuckDuckGoApplication
283303
application.daggerAppComponent.inject(this)
284304
}
305+
306+
companion object {
307+
private const val SEARCH_AND_FAVORITES_WIDGET_REQUEST_CODE = 1540
308+
}
285309
}

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

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,20 @@ import android.view.View
2626
import android.widget.RemoteViews
2727
import com.duckduckgo.app.browser.R
2828
import com.duckduckgo.app.global.DuckDuckGoApplication
29-
import com.duckduckgo.app.global.install.AppInstallStore
30-
import com.duckduckgo.app.pixels.AppPixelName.WIDGETS_ADDED
31-
import com.duckduckgo.app.pixels.AppPixelName.WIDGETS_DELETED
32-
import com.duckduckgo.app.statistics.pixels.Pixel
29+
import com.duckduckgo.app.pixels.AppPixelName.SEARCH_WIDGET_ADDED
30+
import com.duckduckgo.app.pixels.AppPixelName.SEARCH_WIDGET_DELETED
3331
import com.duckduckgo.app.systemsearch.SystemSearchActivity
34-
import com.duckduckgo.app.widget.ui.AppWidgetCapabilities
3532
import javax.inject.Inject
3633

3734
class SearchWidgetLight : SearchWidget(R.layout.search_widget_light)
3835

3936
open class SearchWidget(val layoutId: Int = R.layout.search_widget_dark) : AppWidgetProvider() {
4037

4138
@Inject
42-
lateinit var appInstallStore: AppInstallStore
43-
44-
@Inject
45-
lateinit var pixel: Pixel
46-
47-
@Inject
48-
lateinit var widgetCapabilities: AppWidgetCapabilities
39+
lateinit var voiceSearchWidgetConfigurator: VoiceSearchWidgetConfigurator
4940

5041
@Inject
51-
lateinit var voiceSearchWidgetConfigurator: VoiceSearchWidgetConfigurator
42+
lateinit var searchWidgetLifecycleDelegate: SearchWidgetLifecycleDelegate
5243

5344
override fun onReceive(
5445
context: Context,
@@ -64,10 +55,8 @@ open class SearchWidget(val layoutId: Int = R.layout.search_widget_dark) : AppWi
6455
}
6556

6657
override fun onEnabled(context: Context) {
67-
if (!appInstallStore.widgetInstalled) {
68-
appInstallStore.widgetInstalled = true
69-
pixel.fire(WIDGETS_ADDED)
70-
}
58+
super.onEnabled(context)
59+
searchWidgetLifecycleDelegate.handleOnWidgetEnabled(SEARCH_WIDGET_ADDED)
7160
}
7261

7362
override fun onUpdate(
@@ -120,20 +109,21 @@ open class SearchWidget(val layoutId: Int = R.layout.search_widget_dark) : AppWi
120109

121110
private fun buildPendingIntent(context: Context): PendingIntent {
122111
val intent = SystemSearchActivity.fromWidget(context)
123-
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
112+
return PendingIntent.getActivity(
113+
context,
114+
SEARCH_WIDGET_REQUEST_CODE,
115+
intent,
116+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
117+
)
124118
}
125119

126-
override fun onDeleted(
127-
context: Context,
128-
appWidgetIds: IntArray?,
129-
) {
130-
if (appInstallStore.widgetInstalled && !widgetCapabilities.hasInstalledWidgets) {
131-
appInstallStore.widgetInstalled = false
132-
pixel.fire(WIDGETS_DELETED)
133-
}
120+
override fun onDisabled(context: Context?) {
121+
super.onDisabled(context)
122+
searchWidgetLifecycleDelegate.handleOnWidgetDisabled(SEARCH_WIDGET_DELETED)
134123
}
135124

136125
companion object {
137126
private const val SEARCH_BAR_MIN_HINT_WIDTH_SIZE = 168
127+
private const val SEARCH_WIDGET_REQUEST_CODE = 1530
138128
}
139129
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2025 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 com.duckduckgo.app.global.install.AppInstallStore
20+
import com.duckduckgo.app.pixels.AppPixelName
21+
import com.duckduckgo.app.pixels.AppPixelName.WIDGETS_DELETED
22+
import com.duckduckgo.app.statistics.pixels.Pixel
23+
import com.duckduckgo.app.widget.ui.AppWidgetCapabilities
24+
import javax.inject.Inject
25+
26+
class SearchWidgetLifecycleDelegate @Inject constructor(
27+
private val appInstallStore: AppInstallStore,
28+
private val widgetCapabilities: AppWidgetCapabilities,
29+
private val pixel: Pixel,
30+
) {
31+
32+
fun handleOnWidgetEnabled(widgetSpecificAddedPixel: AppPixelName) {
33+
if (!appInstallStore.widgetInstalled) {
34+
appInstallStore.widgetInstalled = true
35+
pixel.fire(AppPixelName.WIDGETS_ADDED)
36+
}
37+
pixel.fire(widgetSpecificAddedPixel)
38+
}
39+
40+
fun handleOnWidgetDisabled(widgetSpecificDeletedPixel: AppPixelName) {
41+
if (appInstallStore.widgetInstalled && !widgetCapabilities.hasInstalledWidgets) {
42+
appInstallStore.widgetInstalled = false
43+
pixel.fire(WIDGETS_DELETED)
44+
}
45+
pixel.fire(widgetSpecificDeletedPixel)
46+
}
47+
}

0 commit comments

Comments
 (0)