Generated banner for Glance Appwidget Updates And Memory
Generated logo for Glance Appwidget Updates And Memory

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

From the author

Manages Android AppWidget state updates, background syncs, and RemoteViews bitmap memory limits with Jetpack Glance (androidx.glance:glance-appwidget) and AppWidgetProvider receivers on Android 17 (API 37). Use this skill when configuring Android 17 (API level 37) compileSdk/targetSdk and AndroidManifest.xml widget receivers, refreshing frozen or stale Glance widget UI after CoroutineWorker DataStore preference updates (updateAppWidgetState), downsampling images within the WindowMetrics memory limit for SizeMode.Responsive, SizeMode.Exact, or LazyColumn, sharing content:// URIs with FileProvider, or fixing ActionCallback NoSuchMethodException and AppWidgetProvider ForegroundServiceStartNotAllowedException crashes with WorkManager. Don't use for Glance layout templates (suggest the glance-canonical-layouts skill) or in-app Compose screens.
— the description by Google LLC

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

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/android/glance-appwidget-updates-and-memory/download
  2. Unzip 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.

Prerequisites

  • Kotlin Android project (minSdk >= 26) with compileSdk = 37 (targetSdk = 37 when targeting Android 17) in build.gradle.kts.

  • Jetpack Glance (1.2.0, minimum 1.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 existing namespace, 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_server computes the RemoteViews bitmap memory cap (6 * width * height bytes, or 1.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 to QHD+ 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 maxHeightDp in RGB_565 or ALPHA_8), and enforce the per-image byte ceiling (calculateWidgetMemoryCapBytes(context) / distinctImageCount) estimated as 6 * width * height from WindowManager.currentWindowMetrics.bounds on API 30+ (falling back to resources.displayMetrics on API 26-29 or if WindowManager is unavailable).

  • Decode shared bitmaps once outside provideContent : Both SizeMode.Exact (used by glance-canonical-layouts to compose the current span's portrait and landscape orientations) and SizeMode.Responsive (used to pre-bundle a set of DpSize breakpoints) invoke provideContent multiple times per update. Decode shared bitmaps once in provideGlance before provideContent and pass them to ImageProvider(bitmap) so RemoteViews deduplicates the shared Bitmap across all sizes in either mode. If a widget scales distinct bitmaps per LocalSize.current, divide calculateWidgetMemoryCapBytes(context) by the number of distinct bitmaps.

  • Downsample content URIs and grant launcher read permissions : On Android 17 (targetSdk >= 37), bitmap-backed Icons count toward the cap alongside Bitmaps. content:// URIs passed to ImageProvider(uri) or RemoteViews.setImageViewUri don't count toward the inter-process communication (IPC) cap, but the launcher decodes them at full resolution. Downsample the file into context.cacheDir, expose it with FileProvider.getUriForFile, and grant Intent.FLAG_GRANT_READ_URI_PERMISSION to Intent.CATEGORY_HOME packages.

  • Trigger explicit widget updates after state mutations : updateAppWidgetState writes preferences to DataStore without updating the widget UI. After you mutate state, call GlanceAppWidget.update(context, glanceId) or GlanceAppWidget.updateAll(context).

  • Use full updateAppWidget calls when you replace images : In AppWidgetProvider classes, when you replace bitmaps or Icons, call AppWidgetManager.updateAppWidget instead of partiallyUpdateAppWidget.

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:

Detailed steps

Step 1: Configure SDK levels, dependencies, and manifest declarations
  1. Read gradle/libs.versions.toml, app/build.gradle.kts, and app/src/main/AndroidManifest.xml first. If AGP, Kotlin, or library versions are already present, keep them; otherwise, add glance = "1.2.0" along with androidx.glance:glance-appwidget and androidx.glance:glance-material3 to gradle/libs.versions.toml.

  2. In app/build.gradle.kts, keep namespace, applicationId, and any existing minSdk >= 26, targetSdk, or library versions. Set compileSdk = 37 (and targetSdk = 37 only when explicitly migrating the app to target Android 17), enable buildFeatures { compose = true }, and add implementation(libs.androidx.glance.appwidget) and implementation(libs.androidx.glance.material3).

  3. In AndroidManifest.xml (see AndroidManifest.xml), declare <queries> with <action android:name="android.intent.action.MAIN" /> and <category android:name="android.intent.category.HOME" /> so queryIntentActivities discovers launcher packages on API 30+. Register your GlanceAppWidgetReceiver (pointing android.appwidget.provider to @xml/sample_widget_info, see sample_widget_info.xml) and register androidx.core.content.FileProvider with android:authorities="${applicationId}.fileprovider", android:exported="false", android:grantUriPermissions="true", and android.support.FILE_PROVIDER_PATHS pointing 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:

  1. Estimate the cap (calculateWidgetMemoryCapBytes) : On API 30+ (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R), read val bounds = context.getSystemService(WindowManager::class.java)?.currentWindowMetrics?.bounds and, when bounds != null && bounds.width() > 0 && bounds.height() > 0, return 6L * bounds.width().toLong() * bounds.height().toLong() (calling WindowManager from a non-UI widget or worker Context logs a nonfatal StrictMode violation on API 30+, which is expected for this estimate). On API 26-29 (or if WindowManager is unavailable), fall back to context.resources.displayMetrics (6L * dm.widthPixels.toLong() * dm.heightPixels.toLong()). Because system_server caches 6 * width * height once at boot, always size images to the widget's maxWidthDp x maxHeightDp first (Stage 1) rather than filling the full screen budget.

  2. Downsample in two stages (decodeDownsampledWidgetBitmap) :

    • First, probe dimensions with BitmapFactory.Options().apply { inJustDecodeBounds = true; inPreferredConfig = config } and double sampleSize *= 2 while (bounds.outWidth / (sampleSize * 2) >= targetWidthPx && bounds.outHeight / (sampleSize * 2) >= targetHeightPx) || (bounds.outWidth / sampleSize > targetWidthPx * 2) || (bounds.outHeight / sampleSize > targetHeightPx * 2) (where targetWidthPx = 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 doubling sampleSize *= 2 while (max(1, bounds.outWidth / sampleSize).toLong() * max(1, bounds.outHeight / sampleSize).toLong() * bytesPerPixel) > perImageCapBytes. Use 2L bytes per pixel for Bitmap.Config.RGB_565 (opaque photos), 1L for Bitmap.Config.ALPHA_8 masks, and 4L when bounds.outConfig != null && bounds.outConfig != config (because BitmapFactory upgrades translucent PNG or WebP files from RGB_565 to 4-byte ARGB_8888). Decode with inSampleSize = sampleSize and inPreferredConfig = config.

  3. Choose between in-memory Bitmap and FileProvider Uri (cacheWidgetImageUri) : Use decodeDownsampledWidgetBitmap with androidx.glance.ImageProvider(bitmap) when passing bitmaps directly in memory. Use cacheWidgetImageUri when the widget passes a content:// Uri to ImageProvider(uri) or RemoteViews.setImageViewUri: it writes the downsampled bitmap into File(context.cacheDir, "widget_images"), obtains a content:// URI with FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", outFile), queries Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME), and calls context.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:

  1. Choose SizeMode for your layout : Use SizeMode.Exact (recommended when using glance-canonical-layouts to render exact LocalSize.current dimensions for the current grid span's portrait and landscape orientations), SizeMode.Responsive(RESPONSIVE_BUCKETS) (using import androidx.compose.ui.unit.DpSize and import androidx.compose.ui.unit.dp to pre-bundle fixed DpSize breakpoints such as setOf(DpSize(120.dp, 120.dp), DpSize(240.dp, 120.dp), DpSize(240.dp, 240.dp)) so resize transitions don't wake the app process), or SizeMode.Single (for a single layout).

  2. Hoist shared bitmap decodes before provideContent : Because both SizeMode.Exact (2-4 orientation sizes) and SizeMode.Responsive (N buckets) run provideContent multiple times per update, decode shared bitmaps once in provideGlance before provideContent with decodeDownsampledWidgetBitmap and Bitmap.Config.RGB_565 for opaque images (distinctImageCount = 1). Pass the resulting Bitmap to androidx.glance.ImageProvider(sharedBitmap) so RemoteViews deduplicates the shared Bitmap across all sizes.

  3. Provide a zero-bitmap error fallback : Override onCompositionError(context, glanceId, appWidgetId, throwable) and call AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, fallback) with a text-only RemoteViews layout (0 bitmaps).

Step 4: Pre-decode LazyColumn collection images and assign stable itemIds

Read FeedCollectionGlanceWidget.kt when rendering collection widgets with LazyColumn:

  1. Replace import androidx.glance.appwidget.ImageProvider (which accepts Uri) with import androidx.glance.ImageProvider (which accepts Bitmap). Remove Uri.fromFile and Icon.createWithContentUri calls.

  2. While a Glance session is active (~45 seconds after an update), .update() and .updateAll() do not restart provideGlance. Declare var cachedRevision = -1 and var cachedThumbnails = emptyMap<String, Bitmap>() in provideGlance before provideContent. Inside provideContent, read val revision = currentState<Preferences>()[KEY_REVISION] ?: 0, and only when revision != cachedRevision, update cachedRevision = revision and decode the feedItems thumbnails with cachedThumbnails = feedItems.mapNotNull { item -> decodeDownsampledWidgetBitmap(..., distinctImageCount = max(1, feedItems.size), config = Bitmap.Config.RGB_565)?.let { item.id to it } }.toMap() (use mapNotNull rather than associate so the map stays Map<String, Bitmap> instead of Map<String, Bitmap?>). This re-decodes when DataStore state updates during an active session while sharing one decoded Map<String, Bitmap> across all SizeMode.Responsive buckets.

  3. In LazyColumn, pass a stable Long itemId to items(items = feedItems, itemId = { item -> item.id.hashCode().toLong() }) (preferring a persistent numeric ID when available) and render Image(provider = ImageProvider(bitmap), contentDescription = item.title) only when bitmap != null (never fall back to Uri.fromFile in an else branch).

Step 5: Push state updates, configure ActionCallbacks, and use WorkManager

Read WidgetStateAndActions.kt, proguard-rules.pro, and ClassicWidgetImageUpdater.kt to implement these patterns:

  1. Push DataStore updates (requires androidx.datastore:datastore-preferences-core:1.2.1 and import androidx.glance.appwidget.GlanceAppWidgetManager, import androidx.glance.appwidget.state.updateAppWidgetState, and import androidx.glance.appwidget.updateAll): In pushWidgetRevisionUpdate(context) (see WidgetStateAndActions.kt), iterate over for (glanceId in GlanceAppWidgetManager(context).getGlanceIds(Widget::class.java)), mutate state inside updateAppWidgetState(context, glanceId) { prefs -> ... } (passing both context and glanceId), and immediately call widget.update(context, glanceId) (or widget.updateAll(context)).

  2. Use zero-argument ActionCallback classes with ActionParameters.Key and keep <init>() under R8 (requires androidx.work:work-runtime-ktx:2.12.0 when scheduling background work): RunCallbackAction instantiates ActionCallback classes with Class.getDeclaredConstructor().newInstance(), so a constructor parameter fails in every build. Remove constructor parameters from ActionCallback implementations, pass values with ActionParameters.Key and actionParametersOf (see RefreshWidgetAction in WidgetStateAndActions.kt), and add -keep class * implements androidx.glance.appwidget.action.ActionCallback { public <init>(); } to proguard-rules.pro (see proguard-rules.pro). Inside SyncWidgetWorker.doWork(), call pushWidgetRevisionUpdate(applicationContext) so background work updates DataStore and refreshes the widget.

  3. Avoid unconditional one-time WorkManager enqueues in AppWidgetProvider.onUpdate and use full updateAppWidget for images (see ClassicWidgetImageUpdater.kt): Never call context.startForegroundService from AppWidgetProvider.onReceive or onUpdate (which throws ForegroundServiceStartNotAllowedException). GlanceAppWidgetReceiver already keeps a 10-year delayed session worker queued internally so WorkManager's RescheduleReceiver never toggles. In a classic AppWidgetProvider, avoid enqueuing a OneTimeWorkRequest directly inside onUpdate: even with ExistingWorkPolicy.KEEP, once the one-time worker finishes (SUCCEEDED), UnfinishedWorkListener disables RescheduleReceiver, which broadcasts ACTION_PACKAGE_CHANGED and re-triggers onUpdate in a loop. Instead, schedule repeating syncs in onUpdate with enqueueUniquePeriodicWork(..., ExistingPeriodicWorkPolicy.KEEP, ...) (which stays ENQUEUED across runs), use goAsync() for short < 10s updates, or enqueue one-time work (enqueueUniqueWork(..., ExistingWorkPolicy.KEEP, ...)) only from specific action broadcasts in onReceive. When you update a Bitmap or Icon on a classic RemoteViews widget, call appWidgetManager.updateAppWidget rather than partiallyUpdateAppWidget.

Common errors and fixes

Symptom or exceptionRoot causeFix
IllegalArgumentException: RemoteViews for widget update exceeds maximum bitmap memory usageRemoteViews 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 imageHome 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 updateAppWidgetStateupdateAppWidgetState 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 tapActionCallback 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 receiverAppWidgetProvider 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 updateAllMissing 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 Bitmap inside provideContent per size bucket with SizeMode.Responsive or SizeMode.Exact.

  • NEVER size widget bitmaps to fill the full screen memory cap (6 * width * height); always downsample to the widget's target maxWidthDp x maxHeightDp first so unfolding a foldable or switching display resolution after boot does not exceed the boot-time cap.

  • NEVER pass an unscaled content:// Uri or Uri.fromFile to ImageProvider(uri) or RemoteViews.setImageViewUri, or omit context.grantUriPermission for Intent.CATEGORY_HOME.

  • NEVER call updateAppWidgetState without calling GlanceAppWidget.update(context, glanceId) or GlanceAppWidget.updateAll(context).

  • NEVER call partiallyUpdateAppWidget when you replace widget images.

Files

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

View on GitHub

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 Bitmap and bitmap-backed Icon objects (Icon.TYPE_BITMAP and Icon.TYPE_ADAPTIVE_BITMAP) stored in RemoteViews.mBitmapCache (such as calls to setImageViewBitmap, Icon.createWithBitmap, Icon.createWithAdaptiveBitmap, or Jetpack Glance ImageProvider(bitmap)). Each unique Bitmap or bitmap-backed Icon instance in mBitmapCache is counted once per RemoteViews hierarchy.

  • URI-backed images (Icon.createWithContentUri, setImageViewUri, or Glance ImageProvider(uri)) don't count toward the RemoteViews IPC byte cap in system_server, but the widget host process decodes the referenced content:// file at full resolution into its own heap. Always write a downsampled image into context.cacheDir before exposing it through a FileProvider URI.

Mitigation steps

To keep your widget updates within the memory ceiling across phones, tablets, and foldables, do the following:

  1. Compute your runtime memory budget from WindowMetrics: On API level 30 and higher, read the active window bounds from WindowManager.currentWindowMetrics.bounds rather than resources.displayMetrics. On multi-resolution devices and foldables, displayMetrics can report inactive display dimensions. Estimate your total widget image budget at 6L * 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()
    }
    
  2. Downsample images to the widget's viewable dimensions and use RGB_565: Decode bitmaps with power-of-two BitmapFactory.Options.inSampleSize values sized to the widget's maximum display dimensions (maxWidthDp by maxHeightDp), and use Bitmap.Config.RGB_565 (2 bytes per pixel) instead of ARGB_8888 (4 bytes per pixel) for opaque photographs and thumbnails.

  3. Hoist bitmap decoding outside provideContent in Jetpack Glance: When using SizeMode.Responsive or SizeMode.Exact, Glance invokes your provideContent lambda once per size bucket. Decode each shared Bitmap once inside provideGlance before calling provideContent so all size buckets reference the same Bitmap instance in RemoteViews.mBitmapCache.

  4. Use full updateAppWidget calls when replacing images: In classic AppWidgetProvider implementations, avoid calling AppWidgetManager.partiallyUpdateAppWidget to swap Bitmap or Icon views. Partial updates merge new actions into the existing RemoteViews tree in system_server without evicting previously cached image actions. Build a complete RemoteViews layout and call updateAppWidget instead.

  5. Provide an error fallback layout: In Jetpack Glance, override onCompositionError to render a zero-bitmap RemoteViews fallback 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 appwidget

Core 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 InputConnection can retrieve candidate selection data by calling TextAttribute.isTextSuggestionSelected(). These apps should then call AccessibilityEvent.setTextChangeTypes() when dispatching TYPE_VIEW_TEXT_CHANGED events. Apps targeting Android 17 (API level 37) that use the standard TextView will have this feature enabled by default. (That is, TextView will 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_CHANGED events can call AccessibilityEvent.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 legacy MODE_BACKGROUND_ACTIVITY_START_ALLOWED constant. Instead, you should adopt granular controls like MODE_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_ALARM audio 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