Glance Appwidget Updates And Memory
Android (Google)OfficialCreated by Google LLCDistributed by Android (Google)
- Skill
- Tool integration
- Software development
- Apache-2.0
- Low risk
- Has scripts
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a comprehensive workflow for managing Android AppWidget state and memory, specifically addressing the strict bitmap memory limits introduced in Android 17 (API 37). It guides developers through configuring Jetpack Glance and classic AppWidgetProvider implementations to ensure stability, proper image downsampling, and efficient background synchronization. The skill covers memory-efficient bitmap handling, FileProvider integration for content URIs, and robust state management using DataStore and WorkManager to prevent common crashes and UI staleness.
What it does
- Configures Android 17 (API 37) SDK levels and manifest requirements for widgets.
- Implements bitmap downsampling to stay within strict RemoteViews memory limits.
- Provides patterns for deduplicating shared bitmaps across responsive size buckets.
- Guides the use of FileProvider for sharing content URIs securely with launchers.
- Ensures widget UI refreshes after DataStore state mutations.
- Fixes common crashes like NoSuchMethodException and ForegroundServiceStartNotAllowedException.
When to use it
- When targeting Android 17 (API 37) and encountering RemoteViews memory limit exceptions.
- When developing or maintaining Jetpack Glance widgets that display images.
- When migrating classic AppWidgetProvider code to handle background updates safely.
- When debugging frozen or stale widget UI after state updates.
- When implementing responsive widget layouts that require efficient memory management.
Advantages
- Prevents IllegalArgumentException crashes related to widget bitmap memory limits.
- Improves widget performance by optimizing image decoding and deduplication.
- Ensures reliable background updates without violating Android service restrictions.
- Provides clear patterns for modern Glance and legacy AppWidget interoperability.
Usage examples
- “How do I fix the RemoteViews bitmap memory limit exception on Android 17?”
- The agent provides steps to calculate the memory cap using WindowMetrics and implement downsampling helpers to ensure images fit within the system-enforced budget.
- “My Glance widget UI is not updating after I change DataStore preferences.”
- The agent explains how to trigger an explicit update using GlanceAppWidget.update() or updateAll() after calling updateAppWidgetState.
- “How can I safely update images in a classic AppWidgetProvider without causing crashes?”
- The agent guides you to use AppWidgetManager.updateAppWidget instead of partiallyUpdateAppWidget and shows how to schedule background work correctly.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/android/glance-appwidget-updates-and-memory/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip glance-appwidget-updates-and-memory.zip -d .agents/skills
Documentation by Google LLC
This skill provides guidance for managing state updates and keeping Android
AppWidgets that use Jetpack Glance (androidx.glance:glance-appwidget) or
RemoteViews within the Android 17 widget bitmap memory limits. For widget UI
layout templates, suggest the glance-canonical-layouts skill.
When configuring a Glance widget, see Create an app widget with Glance.
When targeting Android 17 (
targetSdk = 37), see Android 17 widget memory limits.When maintaining a classic
AppWidgetProvider, see Create an app widget.
Prerequisites
Kotlin Android project (
minSdk >= 26) withcompileSdk = 37(targetSdk = 37when targeting Android 17) inbuild.gradle.kts.Jetpack Glance (
1.2.0, minimum1.1.1+) and the Compose compiler Gradle plugin enabled on the module.
Critical mandates
Preserve existing package name and toolchain versions : Inspect
build.gradle.kts,gradle/libs.versions.toml, and Kotlin files first. Keep existingnamespace,applicationId,package,targetSdk(unless migrating to Android 17), Android Gradle Plugin (AGP), and Kotlin versions.Downsample images to widget dimensions and estimate the memory cap : Android
system_servercomputes theRemoteViewsbitmap memory cap (6 * width * heightbytes, or1.5 * 4 * width * height) once at boot from the real size of the default display and does not expose this cap in a public API. Because unfolding a foldable or switching display resolution toQHD+after boot can make active window metrics report a larger value than the boot-time cap, always downsample each image first to the widget's target dimensions (maxWidthDp x maxHeightDpinRGB_565orALPHA_8), and enforce the per-image byte ceiling (calculateWidgetMemoryCapBytes(context) / distinctImageCount) estimated as6 * width * heightfromWindowManager.currentWindowMetrics.boundson API 30+ (falling back toresources.displayMetricson API 26-29 or ifWindowManageris unavailable).Decode shared bitmaps once outside provideContent : Both
SizeMode.Exact(used byglance-canonical-layoutsto compose the current span's portrait and landscape orientations) andSizeMode.Responsive(used to pre-bundle a set ofDpSizebreakpoints) invokeprovideContentmultiple times per update. Decode shared bitmaps once inprovideGlancebeforeprovideContentand pass them toImageProvider(bitmap)soRemoteViewsdeduplicates the sharedBitmapacross all sizes in either mode. If a widget scales distinct bitmaps perLocalSize.current, dividecalculateWidgetMemoryCapBytes(context)by the number of distinct bitmaps.Downsample content URIs and grant launcher read permissions : On Android 17 (
targetSdk >= 37), bitmap-backedIcons count toward the cap alongsideBitmaps.content://URIs passed toImageProvider(uri)orRemoteViews.setImageViewUridon't count toward the inter-process communication (IPC) cap, but the launcher decodes them at full resolution. Downsample the file intocontext.cacheDir, expose it withFileProvider.getUriForFile, and grantIntent.FLAG_GRANT_READ_URI_PERMISSIONtoIntent.CATEGORY_HOMEpackages.Trigger explicit widget updates after state mutations :
updateAppWidgetStatewrites preferences to DataStore without updating the widget UI. After you mutate state, callGlanceAppWidget.update(context, glanceId)orGlanceAppWidget.updateAll(context).Use full updateAppWidget calls when you replace images : In
AppWidgetProviderclasses, when you replace bitmaps orIcons, callAppWidgetManager.updateAppWidgetinstead ofpartiallyUpdateAppWidget.
Reference implementation files
Read the companion reference files in references/ when implementing each step,
and update the sample package declaration and class names to match your app:
Bitmap Cap & FileProvider Helper : WidgetBitmapUtils.kt
Responsive Widget, Receiver & Error Fallback : SampleGlanceWidget.kt
LazyColumn Collection Widget & Revision Cache : FeedCollectionGlanceWidget.kt
DataStore State Push, CoroutineWorker & ActionCallback : WidgetStateAndActions.kt
Classic AppWidgetProvider WorkManager & Full Update : ClassicWidgetImageUpdater.kt
Manifest, Widget Info, FileProvider Paths & R8 Rules : AndroidManifest.xml, sample_widget_info.xml, file_paths.xml, proguard-rules.pro
Detailed steps
Step 1: Configure SDK levels, dependencies, and manifest declarations
Read
gradle/libs.versions.toml,app/build.gradle.kts, andapp/src/main/AndroidManifest.xmlfirst. If AGP, Kotlin, or library versions are already present, keep them; otherwise, addglance = "1.2.0"along withandroidx.glance:glance-appwidgetandandroidx.glance:glance-material3togradle/libs.versions.toml.In
app/build.gradle.kts, keepnamespace,applicationId, and any existingminSdk >= 26,targetSdk, or library versions. SetcompileSdk = 37(andtargetSdk = 37only when explicitly migrating the app to target Android 17), enablebuildFeatures { compose = true }, and addimplementation(libs.androidx.glance.appwidget)andimplementation(libs.androidx.glance.material3).In
AndroidManifest.xml(see AndroidManifest.xml), declare<queries>with<action android:name="android.intent.action.MAIN" />and<category android:name="android.intent.category.HOME" />soqueryIntentActivitiesdiscovers launcher packages on API 30+. Register yourGlanceAppWidgetReceiver(pointingandroid.appwidget.providerto@xml/sample_widget_info, see sample_widget_info.xml) and registerandroidx.core.content.FileProviderwithandroid:authorities="${applicationId}.fileprovider",android:exported="false",android:grantUriPermissions="true", andandroid.support.FILE_PROVIDER_PATHSpointing to@xml/file_paths(which declares<cache-path name="widget_cache" path="widget_images/" />, see file_paths.xml):
<br />
<receiver android:name=".glance.MyReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/my_app_widget_info" />
</receiver><br />
Step 2: Estimate the widget memory cap and downsample images
Read WidgetBitmapUtils.kt and implement the memory cap and downsampling helpers:
Estimate the cap (
calculateWidgetMemoryCapBytes) : On API 30+ (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R), readval bounds = context.getSystemService(WindowManager::class.java)?.currentWindowMetrics?.boundsand, whenbounds != null && bounds.width() > 0 && bounds.height() > 0, return6L * bounds.width().toLong() * bounds.height().toLong()(callingWindowManagerfrom a non-UI widget or workerContextlogs a nonfatalStrictModeviolation on API 30+, which is expected for this estimate). On API 26-29 (or ifWindowManageris unavailable), fall back tocontext.resources.displayMetrics(6L * dm.widthPixels.toLong() * dm.heightPixels.toLong()). Becausesystem_servercaches6 * width * heightonce at boot, always size images to the widget'smaxWidthDp x maxHeightDpfirst (Stage 1) rather than filling the full screen budget.Downsample in two stages (
decodeDownsampledWidgetBitmap) :First, probe dimensions with
BitmapFactory.Options().apply { inJustDecodeBounds = true; inPreferredConfig = config }and doublesampleSize *= 2while(bounds.outWidth / (sampleSize * 2) >= targetWidthPx && bounds.outHeight / (sampleSize * 2) >= targetHeightPx) || (bounds.outWidth / sampleSize > targetWidthPx * 2) || (bounds.outHeight / sampleSize > targetHeightPx * 2)(wheretargetWidthPx = max(1, (maxWidthDp * density).toInt())), so wide banners or tall screenshots do not bypass downsampling when one axis already fits.Second, enforce the per-image byte ceiling (
perImageCapBytes = calculateWidgetMemoryCapBytes(context) / max(1, distinctImageCount)) by doublingsampleSize *= 2while(max(1, bounds.outWidth / sampleSize).toLong() * max(1, bounds.outHeight / sampleSize).toLong() * bytesPerPixel) > perImageCapBytes. Use2Lbytes per pixel forBitmap.Config.RGB_565(opaque photos),1LforBitmap.Config.ALPHA_8masks, and4Lwhenbounds.outConfig != null && bounds.outConfig != config(becauseBitmapFactoryupgrades translucent PNG or WebP files fromRGB_565to 4-byteARGB_8888). Decode withinSampleSize = sampleSizeandinPreferredConfig = config.
Choose between in-memory
BitmapandFileProviderUri(cacheWidgetImageUri) : UsedecodeDownsampledWidgetBitmapwithandroidx.glance.ImageProvider(bitmap)when passing bitmaps directly in memory. UsecacheWidgetImageUriwhen the widget passes acontent://UritoImageProvider(uri)orRemoteViews.setImageViewUri: it writes the downsampled bitmap intoFile(context.cacheDir, "widget_images"), obtains acontent://URI withFileProvider.getUriForFile(context, "${context.packageName}.fileprovider", outFile), queriesIntent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME), and callscontext.grantUriPermission(pkg, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION).
Step 3: Deduplicate multi-size bitmaps and handle composition errors
Read SampleGlanceWidget.kt to configure multi-size bitmap deduplication and composition error recovery:
Choose
SizeModefor your layout : UseSizeMode.Exact(recommended when usingglance-canonical-layoutsto render exactLocalSize.currentdimensions for the current grid span's portrait and landscape orientations),SizeMode.Responsive(RESPONSIVE_BUCKETS)(usingimport androidx.compose.ui.unit.DpSizeandimport androidx.compose.ui.unit.dpto pre-bundle fixedDpSizebreakpoints such assetOf(DpSize(120.dp, 120.dp), DpSize(240.dp, 120.dp), DpSize(240.dp, 240.dp))so resize transitions don't wake the app process), orSizeMode.Single(for a single layout).Hoist shared bitmap decodes before
provideContent: Because bothSizeMode.Exact(2-4orientation sizes) andSizeMode.Responsive(Nbuckets) runprovideContentmultiple times per update, decode shared bitmaps once inprovideGlancebeforeprovideContentwithdecodeDownsampledWidgetBitmapandBitmap.Config.RGB_565for opaque images (distinctImageCount = 1). Pass the resultingBitmaptoandroidx.glance.ImageProvider(sharedBitmap)soRemoteViewsdeduplicates the sharedBitmapacross all sizes.Provide a zero-bitmap error fallback : Override
onCompositionError(context, glanceId, appWidgetId, throwable)and callAppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, fallback)with a text-onlyRemoteViewslayout (0bitmaps).
Step 4: Pre-decode LazyColumn collection images and assign stable itemIds
Read FeedCollectionGlanceWidget.kt when rendering collection widgets with
LazyColumn:
Replace
import androidx.glance.appwidget.ImageProvider(which acceptsUri) withimport androidx.glance.ImageProvider(which acceptsBitmap). RemoveUri.fromFileandIcon.createWithContentUricalls.While a Glance session is active (~45 seconds after an update),
.update()and.updateAll()do not restartprovideGlance. Declarevar cachedRevision = -1andvar cachedThumbnails = emptyMap<String, Bitmap>()inprovideGlancebeforeprovideContent. InsideprovideContent, readval revision = currentState<Preferences>()[KEY_REVISION] ?: 0, and only whenrevision != cachedRevision, updatecachedRevision = revisionand decode thefeedItemsthumbnails withcachedThumbnails = feedItems.mapNotNull { item -> decodeDownsampledWidgetBitmap(..., distinctImageCount = max(1, feedItems.size), config = Bitmap.Config.RGB_565)?.let { item.id to it } }.toMap()(usemapNotNullrather thanassociateso the map staysMap<String, Bitmap>instead ofMap<String, Bitmap?>). This re-decodes when DataStore state updates during an active session while sharing one decodedMap<String, Bitmap>across allSizeMode.Responsivebuckets.In
LazyColumn, pass a stableLongitemIdtoitems(items = feedItems, itemId = { item -> item.id.hashCode().toLong() })(preferring a persistent numeric ID when available) and renderImage(provider = ImageProvider(bitmap), contentDescription = item.title)only whenbitmap != null(never fall back toUri.fromFilein anelsebranch).
Step 5: Push state updates, configure ActionCallbacks, and use WorkManager
Read WidgetStateAndActions.kt, proguard-rules.pro, and ClassicWidgetImageUpdater.kt to implement these patterns:
Push DataStore updates (requires
androidx.datastore:datastore-preferences-core:1.2.1andimport androidx.glance.appwidget.GlanceAppWidgetManager,import androidx.glance.appwidget.state.updateAppWidgetState, andimport androidx.glance.appwidget.updateAll): InpushWidgetRevisionUpdate(context)(see WidgetStateAndActions.kt), iterate overfor (glanceId in GlanceAppWidgetManager(context).getGlanceIds(Widget::class.java)), mutate state insideupdateAppWidgetState(context, glanceId) { prefs -> ... }(passing bothcontextandglanceId), and immediately callwidget.update(context, glanceId)(orwidget.updateAll(context)).Use zero-argument
ActionCallbackclasses withActionParameters.Keyand keep<init>()under R8 (requiresandroidx.work:work-runtime-ktx:2.12.0when scheduling background work):RunCallbackActioninstantiatesActionCallbackclasses withClass.getDeclaredConstructor().newInstance(), so a constructor parameter fails in every build. Remove constructor parameters fromActionCallbackimplementations, pass values withActionParameters.KeyandactionParametersOf(seeRefreshWidgetActionin WidgetStateAndActions.kt), and add-keep class * implements androidx.glance.appwidget.action.ActionCallback { public <init>(); }toproguard-rules.pro(see proguard-rules.pro). InsideSyncWidgetWorker.doWork(), callpushWidgetRevisionUpdate(applicationContext)so background work updates DataStore and refreshes the widget.Avoid unconditional one-time
WorkManagerenqueues inAppWidgetProvider.onUpdateand use fullupdateAppWidgetfor images (see ClassicWidgetImageUpdater.kt): Never callcontext.startForegroundServicefromAppWidgetProvider.onReceiveoronUpdate(which throwsForegroundServiceStartNotAllowedException).GlanceAppWidgetReceiveralready keeps a 10-year delayed session worker queued internally so WorkManager'sRescheduleReceivernever toggles. In a classicAppWidgetProvider, avoid enqueuing aOneTimeWorkRequestdirectly insideonUpdate: even withExistingWorkPolicy.KEEP, once the one-time worker finishes (SUCCEEDED),UnfinishedWorkListenerdisablesRescheduleReceiver, which broadcastsACTION_PACKAGE_CHANGEDand re-triggersonUpdatein a loop. Instead, schedule repeating syncs inonUpdatewithenqueueUniquePeriodicWork(..., ExistingPeriodicWorkPolicy.KEEP, ...)(which staysENQUEUEDacross runs), usegoAsync()for short< 10supdates, or enqueue one-time work (enqueueUniqueWork(..., ExistingWorkPolicy.KEEP, ...)) only from specific action broadcasts inonReceive. When you update aBitmaporIconon a classicRemoteViewswidget, callappWidgetManager.updateAppWidgetrather thanpartiallyUpdateAppWidget.
Common errors and fixes
| Symptom or exception | Root cause | Fix |
|---|---|---|
IllegalArgumentException: RemoteViews for widget update exceeds maximum bitmap memory usage | RemoteViews bitmap or icon memory exceeded 6 * W * H bytes. | Downsample to the widget's maxWidthDp x maxHeightDp with inSampleSize and RGB_565 (or ALPHA_8 for masks), enforce calculateWidgetMemoryCapBytes(context) / distinctImageCount, and decode shared bitmaps once before provideContent. |
SecurityException: Permission Denial: opening provider or blank content:// widget image | Home launcher lacks read permission for the FileProvider content:// URI or app passed file:// with Uri.fromFile. | Expose the file with FileProvider, add <queries> for CATEGORY_HOME, and call context.grantUriPermission with Intent.FLAG_GRANT_READ_URI_PERMISSION. |
Widget UI stays frozen after updateAppWidgetState | updateAppWidgetState writes to DataStore without recomposing, or provideGlance was not re-triggered during an active session. | Call .update(context, glanceId) or .updateAll(context) after updateAppWidgetState, and observe revision state inside provideContent. |
NoSuchMethodException: <Class>.<init> [] on action tap | ActionCallback declared constructor parameters or R8 stripped <init>(). | Use a public zero-argument constructor, pass values with ActionParameters.Key, and keep <init>() in ProGuard. |
ForegroundServiceStartNotAllowedException in receiver | AppWidgetProvider called startForegroundService in the background. | Schedule periodic syncs in onUpdate with enqueueUniquePeriodicWork (ExistingPeriodicWorkPolicy.KEEP) or enqueue one-time work (enqueueUniqueWork) from custom action broadcasts in onReceive. |
Unresolved reference: ImageProvider, DpSize, updateAppWidgetState, or updateAll | Missing or mismatched Glance and Compose package imports, or calling updateAppWidgetState(context) without glanceId. | Import androidx.glance.ImageProvider (for Bitmap), androidx.compose.ui.unit.DpSize and androidx.compose.ui.unit.dp (for SizeMode.Responsive), androidx.glance.appwidget.state.updateAppWidgetState (passing both context and glanceId), and androidx.glance.appwidget.updateAll. |
Antipatterns
NEVER decode or scale a
BitmapinsideprovideContentper size bucket withSizeMode.ResponsiveorSizeMode.Exact.NEVER size widget bitmaps to fill the full screen memory cap (
6 * width * height); always downsample to the widget's targetmaxWidthDp x maxHeightDpfirst so unfolding a foldable or switching display resolution after boot does not exceed the boot-time cap.NEVER pass an unscaled
content://UriorUri.fromFiletoImageProvider(uri)orRemoteViews.setImageViewUri, or omitcontext.grantUriPermissionforIntent.CATEGORY_HOME.NEVER call
updateAppWidgetStatewithout callingGlanceAppWidget.update(context, glanceId)orGlanceAppWidget.updateAll(context).NEVER call
partiallyUpdateAppWidgetwhen you replace widget images.
Files
- ATTRIBUTION.mdAdded by AutoGineer1016 B
- LICENSELicence file added by AutoGineer11.1 KiB
- SKILL.md20.8 KiB
- references/AndroidManifest.xml1.1 KiB
- references/ClassicWidgetImageUpdater.ktScript2.0 KiB
- references/FeedCollectionGlanceWidget.ktScript2.3 KiB
- references/SampleGlanceWidget.ktScript4.0 KiB
- references/WidgetBitmapUtils.ktScript4.4 KiB
- references/WidgetStateAndActions.ktScript1.8 KiB
- references/android/about/versions/17/behavior-changes-17.md24.1 KiB
- references/android/develop/ui/compose/glance/create-app-widget.md22.3 KiB
- references/android/develop/ui/views/appwidgets/index.md10.1 KiB
- references/proguard-rules.pro98 B
- references/res/xml/file_paths.xml117 B
- references/res/xml/sample_widget_info.xml307 B
references/android/about/versions/17/behavior-changes-17.md
<br />
Like previous releases, Android 17 includes behavior changes that might affect your app. The following behavior changes apply exclusively to apps that are targeting Android 17 or higher. If your app is targeting Android 17 or higher, you should modify your app to support these behaviors, where applicable.
Be sure to also review the list of behavior changes that affect all apps
running on Android 17 regardless of your app's targetSdkVersion.
[!NOTE] Note: This page lists some of the more important changes. For more detailed information, see the Android 17 release notes.
User experience and system UI
Android 17 includes the following changes that are intended to create a more consistent, intuitive user experience.
Memory limit widget
Beginning with Android 17, the system enforces a strict memory limit on
RemoteViews updates for apps targeting Android 17 (API level 37) or
higher. When an app passes a RemoteViews object to
AppWidgetManager.updateAppWidget,
system_server calculates the total memory footprint of all image data across
every orientation and size bucket in the payload. If the total memory exceeds
the system ceiling (mMaxWidgetBitmapMemory), the system rejects the update and
throws an IllegalArgumentException:
java.lang.IllegalArgumentException: RemoteViews for widget update exceeds maximum bitmap memory usage (used: XXXXX, max: YYYYY)For apps targeting API level 36 or lower, the system logs this memory violation
as a warning (Slog.w) without throwing an exception.
Android skills
Manage AppWidget updates and bitmap memory
Use the AppWidget Updates and Memory Android skill to downsample widget bitmaps, deduplicate responsive images, and stay within Android 17 RemoteViews memory limits across Glance and classic AppWidgetProvider widgets. To install the skill from the Android CLI, run:
android skills add glance-appwidget-updates-and-memory<br />
How the system calculates the memory ceiling
The system sets the widget bitmap memory budget at 6 * width * height bytes
(1.5 * 4 * width * height, equivalent to 1.5 times the size of a full-screen
ARGB_8888 buffer) for the active display configuration. Under API level 37 and
higher, this calculation includes both of the following:
In-memory
Bitmapand bitmap-backedIconobjects (Icon.TYPE_BITMAPandIcon.TYPE_ADAPTIVE_BITMAP) stored inRemoteViews.mBitmapCache(such as calls tosetImageViewBitmap,Icon.createWithBitmap,Icon.createWithAdaptiveBitmap, or Jetpack GlanceImageProvider(bitmap)). Each uniqueBitmapor bitmap-backedIconinstance inmBitmapCacheis counted once perRemoteViewshierarchy.URI-backed images (
Icon.createWithContentUri,setImageViewUri, or GlanceImageProvider(uri)) don't count toward theRemoteViewsIPC byte cap insystem_server, but the widget host process decodes the referencedcontent://file at full resolution into its own heap. Always write a downsampled image intocontext.cacheDirbefore exposing it through aFileProviderURI.
Mitigation steps
To keep your widget updates within the memory ceiling across phones, tablets, and foldables, do the following:
Compute your runtime memory budget from
WindowMetrics: On API level 30 and higher, read the active window bounds fromWindowManager.currentWindowMetrics.boundsrather thanresources.displayMetrics. On multi-resolution devices and foldables,displayMetricscan report inactive display dimensions. Estimate your total widget image budget at6L * bounds.width() * bounds.height()as shown in the following example:private const val FALLBACK_MAX_BYTES = 15_000_000L fun calculateWidgetMemoryCapBytes(context: Context): Long { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val wm = context.getSystemService(WindowManager::class.java) ?: return FALLBACK_MAX_BYTES val bounds = wm.currentWindowMetrics.bounds return 6L * bounds.width().toLong() * bounds.height().toLong() } val dm = context.resources.displayMetrics return 6L * dm.widthPixels.toLong() * dm.heightPixels.toLong() }Downsample images to the widget's viewable dimensions and use
RGB_565: Decode bitmaps with power-of-twoBitmapFactory.Options.inSampleSizevalues sized to the widget's maximum display dimensions (maxWidthDpbymaxHeightDp), and useBitmap.Config.RGB_565(2bytes per pixel) instead ofARGB_8888(4bytes per pixel) for opaque photographs and thumbnails.Hoist bitmap decoding outside
provideContentin Jetpack Glance: When usingSizeMode.ResponsiveorSizeMode.Exact, Glance invokes yourprovideContentlambda once per size bucket. Decode each sharedBitmaponce insideprovideGlancebefore callingprovideContentso all size buckets reference the sameBitmapinstance inRemoteViews.mBitmapCache.Use full
updateAppWidgetcalls when replacing images: In classicAppWidgetProviderimplementations, avoid callingAppWidgetManager.partiallyUpdateAppWidgetto swapBitmaporIconviews. Partial updates merge new actions into the existingRemoteViewstree insystem_serverwithout evicting previously cached image actions. Build a completeRemoteViewslayout and callupdateAppWidgetinstead.Provide an error fallback layout: In Jetpack Glance, override
onCompositionErrorto render a zero-bitmapRemoteViewsfallback if an update fails.
You can inspect your widget's current serialized bitmap memory usage on a connected device by running the following:
adb shell dumpsys appwidgetCore functionality
Android 17 includes the following changes that modify or expand various core capabilities of the Android system.
New lock-free implementation of MessageQueue
Beginning with Android 17, apps targeting Android 17 (API level 37)
or higher receive a new lock-free implementation of
android.os.MessageQueue. The new implementation improves performance and
reduces missed frames, but may break clients that reflect on MessageQueue
private fields and methods.
For more information, including mitigation strategies, see MessageQueue behavior change guidance.
Static final fields are now unmodifiable
Apps running on Android 17 or higher that target
Android 17 (API level 37) or higher cannot change static final fields. If
an app attempts to change a static final field by using reflection, it will
cause an IllegalAccessException. Attempting to modify one of these fields
through JNI APIs (such as SetStaticLongField()) will cause the app to crash.
Accessibility
Android 17 makes the following changes to improve accessibility.
Accessibility support of complex IME physical keyboard typing
This feature introduces new AccessibilityEvent and TextAttribute
APIs to enhance screen reader spoken feedback for CJKV language input. CJKV IME
apps can now signal whether a text conversion candidate has been selected during
text composition. Apps with edit fields can specify text change types when
sending text changed accessibility events.
For example, apps can specify that a text change occurred during text
composition, or that a text change resulted from a commit.
Doing this enables accessibility
services such as screen readers to deliver more precise feedback based on the
nature of the text modification.
App adoption
IME Apps: When setting composing text in edit fields, IMEs can use
TextAttribute.Builder.setTextSuggestionSelected()to indicate whether a specific conversion candidate was selected.Apps with Edit Fields: Apps that maintain a custom
InputConnectioncan retrieve candidate selection data by callingTextAttribute.isTextSuggestionSelected(). These apps should then callAccessibilityEvent.setTextChangeTypes()when dispatchingTYPE_VIEW_TEXT_CHANGEDevents. Apps targeting Android 17 (API level 37) that use the standardTextViewwill have this feature enabled by default. (That is,TextViewwill handle retrieving data from the IME and setting text change types when sending events to accessibility services).Accessibility Services: Accessibility services that process
TYPE_VIEW_TEXT_CHANGEDevents can callAccessibilityEvent.getTextChangeTypes()to identify the nature of the modification and adjust their feedback strategies accordingly.
Privacy
Android 17 includes the following changes to improve user privacy.
ECH (Encrypted Client Hello) enabled
Android 17 introduces platform support for Encrypted Client Hello (ECH), a TLS extension that enhances user privacy by encrypting the Server Name Indication (SNI) in the TLS handshake. This encryption helps prevent network observers from easily identifying the specific domain your app is connecting to.
For apps targeting Android 17 (API level 37) or higher, ECH is used for TLS connections. ECH is active only if the networking library used by the app (for example, HttpEngine, WebView, or OkHttp) has integrated ECH support and the remote server also supports the ECH protocol. If ECH cannot be negotiated, the client sends an ECH extension with randomized contents (a mechanism called ECH GREASE). See RFC 9849 for more details on how ECH GREASE works.
To allow apps to customize this behavior, Android 17 adds a new
<domainEncryption> element to the Network Security Configuration file.
Developers can use <domainEncryption> within <base-config> or
<domain-config> tags to select an ECH mode (for example,
"enabled" or "disabled") on a global or per-domain basis.
For more information, see the Encrypted Client Hello documentation.
Local network permission required for apps targeting Android 17
Android 17 introduces the ACCESS_LOCAL_NETWORK runtime permission
to protect users from unauthorized local network access. Because this falls
under the existing NEARBY_DEVICES permission group, users who have already
granted other NEARBY_DEVICES permissions aren't prompted again. This new
requirement prevents malicious apps from exploiting unrestricted local network
access for covert user tracking and fingerprinting. By declaring and requesting
this permission, your app can discover and connect to devices on the local area
network (LAN), such as smart home devices or casting receivers.
Apps targeting Android 17 (API level 37) or higher now have two paths to maintain communication with LAN devices: Adopt system-mediated, privacy-preserving device pickers to skip the permission prompt, or explicitly request this new permission at runtime to maintain local network communication.
For more information, see the Local network permission documentation.
[!NOTE] Note: In Android 16, apps could opt in to local network permissions. Beginning with Android 17, enforcement is mandatory for apps that target Android 17 (API level 37) or higher.
Hiding passwords from physical devices
If an app targets Android 17 (API level 37) or higher and the user is using
a physical input device (for example, an external keyboard), the Android
operating system applies the new show_passwords_physical setting to all
characters in the password field. By default, that setting hides all password
characters.
The Android system shows the last-typed password character to help the user see if they mistyped the password. However, this is much less necessary with larger external keyboards. In addition, devices with external keyboards often have larger displays, which increases the danger of someone seeing the typed password.
If the user is using the device's touchscreen, the system applies the new
show_passwords_touch setting.
OTP protection for standard SMS messages
Beginning with Android 17, Android is extending its SMS OTP protection
to apply to standard SMS messages (SMS messages containing an OTP that do not
use the WebOTP or SMS Retriever formats). For most apps targeting
Android 17 (API level 37) or higher, these SMS messages do not become
available until three hours after receipt. This delay is intended to help
prevent OTP hijacking. During this three hour delay, the
SMS_RECEIVED_ACTION broadcast is withheld and
SMS provider database queries are filtered. The SMS message is
available to these apps after the delay.
Certain apps such as the default SMS assistant app, connected device companion apps, etc., are exempted from this delay. All apps that rely on reading SMS messages for OTP extraction should transition to using SMS Retriever or SMS User Consent APIs to ensure continued functionality.
[!NOTE] Note: Additional SMS OTP protections for WebOTP and SMS Retriever format messages apply to all apps, regardless of their target API level. For more information, see SMS OTP protection in the Behavior changes for all apps documentation.
Security
Android 17 makes the following improvements to device and app security.
Activity Security
In Android 17, the platform continues its shift toward a "secure-by-default" architecture, introducing a suite of enhancements designed to mitigate high-severity exploits such as phishing, interaction hijacking, and confused deputy attacks. This update requires developers to explicitly opt in to new security standards to maintain app compatibility and user protection.
Key impacts for developers include:
BAL hardening & improved opt-in: We are refining Background Activity Launch (BAL) restrictions by extending protections to
IntentSender. Developers must migrate away from the legacyMODE_BACKGROUND_ACTIVITY_START_ALLOWEDconstant. Instead, you should adopt granular controls likeMODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE, which restricts activity starts to scenarios where the calling app is visible, significantly reducing the attack surface.Adoption tools: Developers should utilize strict mode and updated lint checks to identify legacy patterns and ensure readiness for future target SDK requirements.
Enable CT by default
If an app targets Android 17 (API level 37) or higher, certificate transparency (CT) is enabled by default. (On Android 16, CT is available but apps had to opt in.)
Safer Native DCL---C
If your app targets Android 17 (API level 37) or higher, the Safer Dynamic Code Loading (DCL) protection introduced in Android 14 for DEX and JAR files now extends to native libraries.
All native files loaded using System.load() must be marked as read-only.
Otherwise, the system throws UnsatisfiedLinkError.
We recommend that apps avoid dynamically loading code whenever possible, as doing so greatly increases the risk that an app can be compromised by code injection or code tampering.
Restrict PII fields in CP2 data view
For apps targeting Android 17 (API level 37) and higher, Contacts Provider 2 (CP2) restricts certain columns containing Personally Identifiable Information (PII) from the data view. When this change is enabled, these columns are removed from the data view to enhance user privacy. The restricted columns include:
Apps that are using these columns from ContactsContract.Data
can extract them from ContactsContract.RawContacts
instead, by joining with RAW_CONTACT_ID.
Enforce strict SQL checks in CP2
For apps targeting Android 17 (API level 37) and
higher, Contacts Provider 2 (CP2) enforces strict SQL query validation when
the ContactsContract.Data table is accessed without
READ_CONTACTS permission.
With this change, if an app doesn't have READ_CONTACTS
permission, StrictColumns and
StrictGrammar options are set when querying
the ContactsContract.Data table. If a query
uses a pattern that isn't compatible with these, it will be
rejected and cause an exception to be thrown.
Intelligence
Android 17 includes the following changes to system intelligence.
Deprecation of setContentCaptureEnabled
Content Capture is enabled by default on certain devices to allow on-device AI features to analyze screen contents for intelligent experiences.
Starting in Android 17, the
ContentCaptureManager.setContentCaptureEnabled(boolean)
API method is deprecated. For apps that target Android 17 (API level 37) or
higher, calling setContentCaptureEnabled(false) no longer disables Content
Capture.
If your app needs to continue disabling Content Capture or restrict screen
contents from being captured by the system, you must transition to using the
FLAG_SECURE window layout parameter.
To disable Content Capture, set the FLAG_SECURE flag on your window as shown
in the following example:
Kotlin
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)Java
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
);For more details, see the
WindowManager.LayoutParams.FLAG_SECURE reference
documentation.
Media
Android 17 includes the following changes to media behavior.
Background audio hardening
Beginning with Android 17, the audio framework enforces restrictions on background audio interactions including audio playback, audio focus requests, and volume change APIs to ensure that these changes are started intentionally by the user.
Some audio restrictions apply to all apps. However, the restrictions are more stringent if an app targets Android 17 (API level 37). If one of these apps interacts with audio while it is in the background, it must have a foreground service running. In addition, the app must meet one or both of these requirements:
The foreground service must have while-in-use (WIU) capabilities.
The app must have the exact alarm permission and be interacting with
USAGE_ALARMaudio streams.
For more information, including mitigation strategies, see Background audio hardening.
Device form factors
Android 17 includes the following changes to improve user experience across a range of device sizes and form factors.
Platform API changes to ignore orientation, resizability, and aspect ratio constraints on large screens (sw>=600dp)
We introduced Platform API changes in Android 16 to ignore orientation, resizability, and aspect ratio restrictions on large screens (sw >= 600dp) for apps targeting API level 36 or higher. Developers have the option to opt out of these changes with SDK 36, but this opt-out will no longer be available for apps that target Android 17 (API level 37) or higher.
For more information, see Restrictions on orientation and resizability are ignored.
Connectivity
Android 17 introduces the following change to improve consistency and
align with standard Java InputStream behavior for Bluetooth RFCOMM sockets.
Consistent BluetoothSocket read() behavior for RFCOMM
For apps targeting Android 17 (API level 37), the
read() method of the InputStream obtained from an
RFCOMM-based BluetoothSocket now returns -1 when the
socket is closed or the connection is dropped.
This change makes RFCOMM socket behavior consistent with LE CoC sockets and
aligns with the standard InputStream.read()
documentation, which states that -1 is returned when the end of the stream is
reached.
Apps that rely solely on catching an IOException to break out of a read loop may
be impacted by this change and should update the BluetoothSocket read loops to
explicitly check for a return value of -1. This ensures the loop terminates
correctly when the remote device disconnects or the socket is closed. For an
example of the recommended implementation, see the
code snippet in the Transfer Bluetooth data
guide.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
What our scanner noticed· Low risk
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Google LLC
- Publisher
- Android (Google) (Official)
- Distributed by
- Android (Google)
- Source
- android/skills · system/glance-appwidget-updates-and-memory · commit bf68e2d
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE.txt)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Google LLC or Android (Google).
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download glance-appwidget-updates-and-memory.zip