From f2ffba167911e3ca6d868ef187d77ab9087ec8a7 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Thu, 25 Apr 2024 17:08:46 +0200 Subject: [PATCH] never create more than the allowed number of shortcuts (#4389) The only crash so far in the 25.0-beta1 crash reports. Probably not a regression though as that code did not change in a while. ``` Exception java.lang.IllegalArgumentException: Max number of dynamic shortcuts exceeded at android.os.Parcel.createExceptionOrNull (Parcel.java:3032) at android.os.Parcel.createException (Parcel.java:3012) at android.os.Parcel.readException (Parcel.java:2995) at android.os.Parcel.readException (Parcel.java:2937) at android.content.pm.IShortcutService$Stub$Proxy.addDynamicShortcuts (IShortcutService.java:618) at android.content.pm.ShortcutManager.addDynamicShortcuts (ShortcutManager.java:240) at androidx.core.content.pm.ShortcutManagerCompat.addDynamicShortcuts (ShortcutManagerCompat.java:334) at com.keylesspalace.tusky.util.ShareShortcutHelper$updateShortcut$1.invokeSuspend (ShareShortcutHelper.kt:96) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith (ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run (DispatchedTask.kt:104) at android.os.Handler.handleCallback (Handler.java:984) at android.os.Handler.dispatchMessage (Handler.java:104) at android.os.Looper.loopOnce (Looper.java:238) at android.os.Looper.loop (Looper.java:357) at android.app.ActivityThread.main (ActivityThread.java:8094) at java.lang.reflect.Method.invoke at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:957) Caused by android.os.RemoteException: Remote stack trace: at com.android.server.pm.ShortcutService.enforceMaxActivityShortcuts (ShortcutService.java:1768) at com.android.server.pm.ShortcutPackage.enforceShortcutCountsBeforeOperation (ShortcutPackage.java:1551) at com.android.server.pm.ShortcutService.addDynamicShortcuts (ShortcutService.java:2161) at android.content.pm.IShortcutService$Stub.onTransact (IShortcutService.java:281) at android.os.Binder.execTransactInternal (Binder.java:1294) ``` --- .../com/keylesspalace/tusky/MainActivity.kt | 2 +- .../tusky/util/ShareShortcutHelper.kt | 93 ++++++++++--------- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index 499d080f7..3adb29f2a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -1066,7 +1066,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje } updateProfiles() - shareShortcutHelper.updateShortcut(accountManager.activeAccount!!) + shareShortcutHelper.updateShortcuts() } @SuppressLint("CheckResult") diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ShareShortcutHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/ShareShortcutHelper.kt index 11f89255e..d35e3af5a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ShareShortcutHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ShareShortcutHelper.kt @@ -21,7 +21,6 @@ import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.graphics.Canvas -import android.text.TextUtils import androidx.core.app.Person import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat @@ -30,70 +29,72 @@ import com.bumptech.glide.Glide import com.keylesspalace.tusky.MainActivity import com.keylesspalace.tusky.R import com.keylesspalace.tusky.db.AccountEntity +import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.di.ApplicationScope import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class ShareShortcutHelper @Inject constructor( private val context: Context, + private val accountManager: AccountManager, @ApplicationScope private val externalScope: CoroutineScope ) { - fun updateShortcut(account: AccountEntity) { - externalScope.launch { + fun updateShortcuts() { + externalScope.launch(Dispatchers.IO) { val innerSize = context.resources.getDimensionPixelSize(R.dimen.adaptive_bitmap_inner_size) val outerSize = context.resources.getDimensionPixelSize(R.dimen.adaptive_bitmap_outer_size) - val bmp = if (TextUtils.isEmpty(account.profilePictureUrl)) { - Glide.with(context) - .asBitmap() - .load(R.drawable.avatar_default) - .submitAsync(innerSize, innerSize) - } else { - Glide.with(context) + val maxNumberOfShortcuts = ShortcutManagerCompat.getMaxShortcutCountPerActivity(context) + + val shortcuts = accountManager.accounts.take(maxNumberOfShortcuts).map { account -> + + val bmp = Glide.with(context) .asBitmap() .load(account.profilePictureUrl) + .placeholder(R.drawable.avatar_default) .error(R.drawable.avatar_default) .submitAsync(innerSize, innerSize) + + // inset the loaded bitmap inside a 108dp transparent canvas so it looks good as adaptive icon + val outBmp = Bitmap.createBitmap(outerSize, outerSize, Bitmap.Config.ARGB_8888) + + val canvas = Canvas(outBmp) + canvas.drawBitmap( + bmp, + (outerSize - innerSize).toFloat() / 2f, + (outerSize - innerSize).toFloat() / 2f, + null + ) + + val icon = IconCompat.createWithAdaptiveBitmap(outBmp) + + val person = Person.Builder() + .setIcon(icon) + .setName(account.displayName) + .setKey(account.identifier) + .build() + + // This intent will be sent when the user clicks on one of the launcher shortcuts. Intent from share sheet will be different + val intent = Intent(context, MainActivity::class.java).apply { + action = Intent.ACTION_SEND + type = "text/plain" + putExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID, account.id.toString()) + } + + ShortcutInfoCompat.Builder(context, account.id.toString()) + .setIntent(intent) + .setCategories(setOf("com.keylesspalace.tusky.Share")) + .setShortLabel(account.displayName) + .setPerson(person) + .setLongLived(true) + .setIcon(icon) + .build() } - // inset the loaded bitmap inside a 108dp transparent canvas so it looks good as adaptive icon - val outBmp = Bitmap.createBitmap(outerSize, outerSize, Bitmap.Config.ARGB_8888) - - val canvas = Canvas(outBmp) - canvas.drawBitmap( - bmp, - (outerSize - innerSize).toFloat() / 2f, - (outerSize - innerSize).toFloat() / 2f, - null - ) - - val icon = IconCompat.createWithAdaptiveBitmap(outBmp) - - val person = Person.Builder() - .setIcon(icon) - .setName(account.displayName) - .setKey(account.identifier) - .build() - - // This intent will be sent when the user clicks on one of the launcher shortcuts. Intent from share sheet will be different - val intent = Intent(context, MainActivity::class.java).apply { - action = Intent.ACTION_SEND - type = "text/plain" - putExtra(ShortcutManagerCompat.EXTRA_SHORTCUT_ID, account.id.toString()) - } - - val shortcutInfo = ShortcutInfoCompat.Builder(context, account.id.toString()) - .setIntent(intent) - .setCategories(setOf("com.keylesspalace.tusky.Share")) - .setShortLabel(account.displayName) - .setPerson(person) - .setLongLived(true) - .setIcon(icon) - .build() - - ShortcutManagerCompat.addDynamicShortcuts(context, listOf(shortcutInfo)) + ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts) } }