Skip to content

Commit

Permalink
Refactor: Safely retrieve AppWidgetManager instance to prevent potent…
Browse files Browse the repository at this point in the history
…ial crashes

- Added getAppWidgetManager method to safely get AppWidgetManager instance and handle exceptions.
- Updated doUpdate method in UpdateService to use getAppWidgetManager and return early if null.
- Changed onStart to onStartCommand and added doUpdate call.
- Added comments explaining the changes and the rationale.

This ensures that we handle any potential null values from AppWidgetManager.getInstance and prevent crashes that could occur on some devices.
  • Loading branch information
xenonnn4w committed Jul 13, 2024
1 parent 6c4365e commit c112c30
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,24 @@ abstract class AnalyticsWidgetProvider : AppWidgetProvider() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
if (!IntentHandler.grantedStoragePermissions(context, showToast = false)) {
Timber.w("Opening widget without storage access")
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)
}
41 changes: 28 additions & 13 deletions AnkiDroid/src/main/java/com/ichi2/widget/AnkiDroidWidgetSmall.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ import android.util.TypedValue
import android.view.View
import android.widget.RemoteViews
import androidx.core.app.PendingIntentCompat
import androidx.core.content.ContextCompat
import com.ichi2.anki.AnkiDroidApp
import com.ichi2.anki.IntentHandler
import com.ichi2.anki.R
import com.ichi2.anki.analytics.UsageAnalytics
import com.ichi2.compat.CompatHelper.Companion.registerReceiverCompat
import com.ichi2.utils.KotlinCleanup
import timber.log.Timber
import kotlin.math.sqrt
Expand All @@ -39,7 +41,6 @@ import kotlin.math.sqrt
*/

class AnkiDroidWidgetSmall : AnalyticsWidgetProvider() {

override fun performUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
Expand All @@ -62,17 +63,23 @@ class AnkiDroidWidgetSmall : AnalyticsWidgetProvider() {
private var dueCardsCount = 0

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

override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Timber.i("SmallWidget: onStartCommand")
doUpdate(this)
return START_NOT_STICKY
}

@Deprecated("Implement onStartCommand(Intent, int, int) instead.") // TODO
override fun onStart(intent: Intent, startId: Int) {
Timber.i("SmallWidget: OnStart")
val updateViews = buildUpdate(this, true)
val thisWidget = ComponentName(this, AnkiDroidWidgetSmall::class.java)
val manager = AppWidgetManager.getInstance(this)
manager.updateAppWidget(thisWidget, updateViews)
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 @@ -109,7 +116,7 @@ class AnkiDroidWidgetSmall : AnalyticsWidgetProvider() {
val iFilter = IntentFilter()
iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED)
iFilter.addDataScheme("file")
AnkiDroidApp.instance.registerReceiver(mMountReceiver, iFilter)
AnkiDroidApp.instance.registerReceiverCompat(mMountReceiver, iFilter, ContextCompat.RECEIVER_EXPORTED)
}
} else {
// If we do not have a cached version, always update.
Expand Down Expand Up @@ -168,9 +175,8 @@ class AnkiDroidWidgetSmall : AnalyticsWidgetProvider() {
companion object {
private var mMountReceiver: BroadcastReceiver? = null
private var remounted = false

private fun updateWidgetDimensions(context: Context, updateViews: RemoteViews, cls: Class<*>) {
val manager = AppWidgetManager.getInstance(context)
val manager = getAppWidgetManager(context) ?: return
val ids = manager.getAppWidgetIds(ComponentName(context, cls))
for (id in ids) {
val scale = context.resources.displayMetrics.density
Expand Down Expand Up @@ -201,5 +207,14 @@ class AnkiDroidWidgetSmall : AnalyticsWidgetProvider() {
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
}
}
}
}

0 comments on commit c112c30

Please sign in to comment.