Skip to content

Commit

Permalink
Adding Up Analytical widget provider class for Deck Picker Widget .
Browse files Browse the repository at this point in the history
  • Loading branch information
xenonnn4w committed Jul 15, 2024
1 parent ec27927 commit 11ac49e
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 53 deletions.
27 changes: 8 additions & 19 deletions AnkiDroid/src/main/java/com/ichi2/widget/AddNoteWidget.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,24 @@
package com.ichi2.widget

import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import androidx.core.app.PendingIntentCompat
import com.ichi2.anki.IntentHandler
import com.ichi2.anki.NoteEditor
import com.ichi2.anki.R
import com.ichi2.anki.analytics.UsageAnalytics
import timber.log.Timber

class AddNoteWidget : AppWidgetProvider() {
override fun onEnabled(context: Context) {
super.onEnabled(context)
UsageAnalytics.sendAnalyticsEvent(this.javaClass.simpleName, "enabled")
}

override fun onDisabled(context: Context) {
super.onDisabled(context)
UsageAnalytics.sendAnalyticsEvent(this.javaClass.simpleName, "disabled")
}
class AddNoteWidget : AnalyticsWidgetProvider() {

override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
if (!IntentHandler.grantedStoragePermissions(context, showToast = false)) {
Timber.w("Opening AddNote widget without storage access")
return
}
Timber.d("onUpdate")
override fun performUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
usageAnalytics: UsageAnalytics
) {
Timber.d("AddNoteWidget: performUpdate")
updateWidgets(context, appWidgetManager, appWidgetIds)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2024 Anoop <[email protected]>
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.ichi2.widget

import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import androidx.annotation.CallSuper
import com.ichi2.anki.IntentHandler
import com.ichi2.anki.analytics.UsageAnalytics
import timber.log.Timber

/**
* AnalyticsWidgetProvider is an abstract base class for App Widgets that integrates
* with UsageAnalytics to send analytics events when the widget is enabled, disabled,
* or updated.
*
* This class should always be used as the base class for App Widgets in this application.
* Direct usage of AppWidgetProvider should be avoided.
* TODO: Add a lint rule to forbid the direct use of AppWidgetProvider.
*
* Subclasses must override [performUpdate] to define the widget update logic.
*
* - To use this class, extend it and implement the [performUpdate] method.
* - Override [onUpdate] if additional logic is required beyond [performUpdate].
*/
abstract class AnalyticsWidgetProvider : AppWidgetProvider() {

/**
* Called when the widget is enabled. Sends an analytics event.
*
* @param context The context in which the receiver is running.
*/
@CallSuper
override fun onEnabled(context: Context) {
super.onEnabled(context)
UsageAnalytics.sendAnalyticsEvent(this.javaClass.simpleName, "enabled")
}

/**
* Called when the widget is disabled. Sends an analytics event.
*
* @param context The context in which the receiver is running.
*/
@CallSuper
override fun onDisabled(context: Context) {
super.onDisabled(context)
UsageAnalytics.sendAnalyticsEvent(this.javaClass.simpleName, "disabled")
}

/**
* Called to update the widget. Checks storage permissions and delegates to [performUpdate].
*
* @param context The context in which the receiver is running.
* @param appWidgetManager The AppWidgetManager instance to use for updating widgets.
* @param appWidgetIds The app widget IDs to update.
*/
@CallSuper
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
if (!IntentHandler.grantedStoragePermissions(context, showToast = false)) {
Timber.w("Opening widget ${this.javaClass.name} without storage access")
return
}
// Pass usageAnalytics to performUpdate
performUpdate(context, appWidgetManager, appWidgetIds, UsageAnalytics)
Timber.d("${this.javaClass.name}: performUpdate")
}

/**
* Abstract method to be implemented by subclasses to perform widget updates.
* <p>
* Note: When this method is executed, it is assumed that the storage access is granted.
*
* @param context The context in which the receiver is running.
* @param appWidgetManager The AppWidgetManager instance to use for updating widgets.
* @param appWidgetIds The app widget IDs to update.
* @param usageAnalytics The UsageAnalytics instance for logging analytics events.
*/

abstract fun performUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, usageAnalytics: UsageAnalytics)
}
71 changes: 37 additions & 34 deletions AnkiDroid/src/main/java/com/ichi2/widget/AnkiDroidWidgetSmall.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package com.ichi2.widget
import android.app.PendingIntent
import android.app.Service
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.*
import android.content.res.Configuration
import android.os.IBinder
Expand All @@ -26,42 +25,30 @@ import android.view.View
import android.widget.RemoteViews
import androidx.core.app.PendingIntentCompat
import androidx.core.content.ContextCompat
import androidx.core.content.edit
import com.ichi2.anki.AnkiDroidApp
import com.ichi2.anki.IntentHandler
import com.ichi2.anki.IntentHandler.Companion.grantedStoragePermissions
import com.ichi2.anki.R
import com.ichi2.anki.analytics.UsageAnalytics
import com.ichi2.anki.preferences.sharedPrefs
import com.ichi2.compat.CompatHelper.Companion.registerReceiverCompat
import com.ichi2.utils.KotlinCleanup
import timber.log.Timber
import kotlin.math.sqrt

class AnkiDroidWidgetSmall : AppWidgetProvider() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
Timber.d("SmallWidget: onUpdate")
if (!grantedStoragePermissions(context, showToast = false)) {
Timber.w("Opening AnkiDroid Small widget without storage access")
return
}
WidgetStatus.updateInBackground(context)
}
/**
* AnkiDroidWidgetSmall is a small-sized home screen widget for the AnkiDroid application.
* This widget displays the number of due cards and an estimated review time.
* It updates periodically and can respond to certain actions like resizing.
*/

override fun onEnabled(context: Context) {
super.onEnabled(context)
Timber.d("SmallWidget: Widget enabled")
val preferences = context.sharedPrefs()
preferences.edit(commit = true) { putBoolean("widgetSmallEnabled", true) }
UsageAnalytics.sendAnalyticsEvent(this.javaClass.simpleName, "enabled")
}

override fun onDisabled(context: Context) {
super.onDisabled(context)
Timber.d("SmallWidget: Widget disabled")
val preferences = context.sharedPrefs()
preferences.edit(commit = true) { putBoolean("widgetSmallEnabled", false) }
UsageAnalytics.sendAnalyticsEvent(this.javaClass.simpleName, "disabled")
class AnkiDroidWidgetSmall : AnalyticsWidgetProvider() {
override fun performUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
usageAnalytics: UsageAnalytics
) {
Timber.d("SmallWidget: performUpdate")
WidgetStatus.updateInBackground(context)
}

override fun onReceive(context: Context, intent: Intent) {
Expand All @@ -74,18 +61,25 @@ class AnkiDroidWidgetSmall : AppWidgetProvider() {
class UpdateService : Service() {
/** The cached number of total due cards. */
private var dueCardsCount = 0

fun doUpdate(context: Context) {
val appWidgetManager = getAppWidgetManager(context) ?: return
appWidgetManager.updateAppWidget(ComponentName(context, AnkiDroidWidgetSmall::class.java), buildUpdate(context, true))
}

@Deprecated("Implement onStartCommand(Intent, int, int) instead.") // TODO
override fun onStart(intent: Intent, startId: Int) {
Timber.i("SmallWidget: OnStart")
val manager = getAppWidgetManager(this) ?: return
val updateViews = buildUpdate(this, true)
val thisWidget = ComponentName(this, AnkiDroidWidgetSmall::class.java)
manager.updateAppWidget(thisWidget, updateViews)
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Timber.i("SmallWidget: onStartCommand")
doUpdate(this)
return START_NOT_STICKY
}

private fun getAppWidgetManager(context: Context): AppWidgetManager? {
return try {
AppWidgetManager.getInstance(context)
} catch (e: Exception) {
Timber.e(e, "Failed to get AppWidgetManager instance")
null
}
}

@KotlinCleanup("Fix param updateDueDecksNow always true")
Expand Down Expand Up @@ -213,5 +207,14 @@ class AnkiDroidWidgetSmall : AppWidgetProvider() {
updateViews.setViewPadding(R.id.ankidroid_widget_text_layout, horizontal, vertical, horizontal, vertical)
}
}

private fun getAppWidgetManager(context: Context): AppWidgetManager? {
return try {
AppWidgetManager.getInstance(context)
} catch (e: Exception) {
Timber.e(e, "Failed to get AppWidgetManager instance")
null
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (c) 2024 David Allison <[email protected]>
* Copyright (c) 2024 Anoop <[email protected]>
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/

import android.appwidget.AppWidgetManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.anki.RobolectricTest
import com.ichi2.anki.analytics.UsageAnalytics
import com.ichi2.widget.AnalyticsWidgetProvider
import io.mockk.every
import io.mockk.mockkObject
import io.mockk.unmockkObject
import io.mockk.verify
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class DeckPickerWidgetTest : RobolectricTest() {

@Before
override fun setUp() {
super.setUp()
mockkObject(UsageAnalytics)
every { UsageAnalytics.sendAnalyticsEvent(any(), any()) } answers { }
}

@After
override fun tearDown() {
super.tearDown()
unmockkObject(UsageAnalytics)
}

@Test
fun testAnalyticsEventLogging() {
val widgetProvider = TestWidgetProvider()

widgetProvider.onEnabled(targetContext)

verify { UsageAnalytics.sendAnalyticsEvent("TestWidgetProvider", "enabled") }
}

private class TestWidgetProvider : AnalyticsWidgetProvider() {
override fun performUpdate(
context: android.content.Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
usageAnalytics: UsageAnalytics
) {
// Do nothing
}
}
}

0 comments on commit 11ac49e

Please sign in to comment.