Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ material3Android = "1.3.2"
media3 = "1.5.0"
constraintlayout = "2.1.4"
glide-compose = "1.0.0-beta01"
glance = "1.1.0"
glance = "1.3.0-alpha01"
tensorflowLite = "2.9.0"
tensorflowLiteGpuDelegatePlugin = "0.4.4"
tensorflowLiteSupport = "0.4.2"
Expand Down
4 changes: 2 additions & 2 deletions samples/user-interface/appwidgets/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ plugins {

android {
namespace = "com.example.platform.ui.appwidgets"
compileSdk = 36
compileSdk = 37

defaultConfig {
minSdk = 21
minSdk = 23
}

compileOptions {
Expand Down
13 changes: 13 additions & 0 deletions samples/user-interface/appwidgets/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,19 @@
android:name="android.appwidget.provider"
android:resource="@xml/sample_text_with_image_widget_info" />
</receiver>
<receiver
android:name=".glance.layout.text.FullBleedImageAppWidgetReceiver"
android:enabled="@bool/glance_appwidget_available"
android:exported="false"
android:label="@string/sample_full_bleed_image_app_widget_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.intent.action.LOCALE_CHANGED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/sample_full_bleed_image_widget_info" />
</receiver>

<!-- List of image + text -->
<receiver
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
/*
* Copyright 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.platform.ui.appwidgets.glance.layout

import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.annotation.DrawableRes
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
Expand Down Expand Up @@ -53,12 +72,15 @@ import com.example.platform.ui.appwidgets.glance.layout.collections.ImageGridApp
import com.example.platform.ui.appwidgets.glance.layout.collections.ImageTextListAppWidgetReceiver
import com.example.platform.ui.appwidgets.glance.layout.text.LongTextAppWidgetReceiver
import com.example.platform.ui.appwidgets.glance.layout.text.TextWithImageAppWidgetReceiver
import com.example.platform.ui.appwidgets.glance.layout.text.FullBleedImageAppWidgetReceiver
import com.example.platform.ui.appwidgets.glance.layout.toolbars.ExpressiveToolbarAppWidgetReceiver
import com.example.platform.ui.appwidgets.glance.layout.toolbars.SearchToolBarAppWidgetReceiver
import com.example.platform.ui.appwidgets.glance.layout.toolbars.ToolBarAppWidgetReceiver
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

import androidx.lifecycle.lifecycleScope

class CanonicalLayoutActivity : ComponentActivity() {

companion object {
Expand All @@ -72,6 +94,35 @@ class CanonicalLayoutActivity : ComponentActivity() {
enableEdgeToEdge()

super.onCreate(savedInstanceState)

// Publish Generated Widget Previews
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
lifecycleScope.launch {
try {
val context = this@CanonicalLayoutActivity
val receiver = FullBleedImageAppWidgetReceiver::class.java
val glanceAppWidgetManager = GlanceAppWidgetManager(context)
val appWidgetManager = context.getSystemService(AppWidgetManager::class.java)

val providerInfo = appWidgetManager?.installedProviders?.firstOrNull {
it.provider.className == receiver.name
}

if (providerInfo?.generatedPreviewCategories == 0) {
val result = glanceAppWidgetManager.setWidgetPreviews(FullBleedImageAppWidgetReceiver::class)
val status = when (result) {
GlanceAppWidgetManager.SET_WIDGET_PREVIEWS_RESULT_SUCCESS -> "Success"
GlanceAppWidgetManager.SET_WIDGET_PREVIEWS_RESULT_RATE_LIMITED -> "Rate-Limited"
else -> "Error ($result)"
}
Log.i("CanonicalLayoutActivity", "Published previews for ${receiver.simpleName}: $status")
}
} catch (e: Exception) {
Log.e("CanonicalLayoutActivity", "Failed to set widget previews", e)
}
}
}

setContent {
MaterialTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Expand Down Expand Up @@ -195,7 +246,7 @@ private fun CanonicalLayoutRow(widget: CanonicalLayoutRowData, modifier: Modifie
painter = painterResource(id = widget.imageRes),
contentDescription = "Screenshot of ${widget.rowTitle}",
contentScale = ContentScale.FillWidth,
modifier = modifier,
modifier = modifier.fillMaxWidth(),
)
}

Expand Down Expand Up @@ -287,6 +338,12 @@ private val canonicalLayoutWidgets = listOf(
imageRes = R.drawable.cl_activity_row_text_image,
receiver = TextWithImageAppWidgetReceiver::class.java,
),
CanonicalLayoutRowData(
rowTitle = R.string.cl_title_full_bleed_image,
rowDescription = R.string.cl_description_full_bleed_image,
imageRes = R.drawable.cl_activity_row_full_bleed_image,
receiver = FullBleedImageAppWidgetReceiver::class.java,
),
CanonicalLayoutRowData(
rowTitle = R.string.cl_title_grid,
rowDescription = R.string.cl_description_grid,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
/*
* Copyright 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.platform.ui.appwidgets.glance.layout.collections.data

import android.content.Context
Expand All @@ -16,11 +32,11 @@ import com.example.platform.ui.appwidgets.glance.layout.collections.layout.Image
import com.example.platform.ui.appwidgets.glance.layout.utils.ImageUtils.getMaxPossibleImageSize
import com.example.platform.ui.appwidgets.glance.layout.utils.ImageUtils.getMaxWidgetMemoryAllowedSizeInBytes
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
import com.example.platform.ui.appwidgets.glance.layout.computeIfAbsent as computeIfAbsentExt
/**
* A fake in-memory implementation of repository that produces a list of
Expand Down Expand Up @@ -74,7 +90,7 @@ class FakeImageGridDataRepository {
val width = IMAGE_SIZE.coerceAtMost(imageSizeLimit.width)
val height = width * 9 / 16

val mappedItems = runBlocking {
val mappedItems = coroutineScope {
items.map { item ->
async(Dispatchers.IO) {
var bitmap: Bitmap? = null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the details; and limitations under the License.
*/

package com.example.platform.ui.appwidgets.glance.layout.text

import android.content.Context
import android.util.Log
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.glance.GlanceId
import androidx.glance.GlanceTheme
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetManager
import androidx.glance.appwidget.GlanceAppWidgetReceiver
import androidx.glance.appwidget.SizeMode
import androidx.glance.appwidget.provideContent
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import com.example.platform.ui.appwidgets.glance.layout.collections.data.FakeImageGridDataRepository
import com.example.platform.ui.appwidgets.glance.layout.collections.data.FakeImageGridDataRepository.Companion.getImageGridDataRepo
import com.example.platform.ui.appwidgets.glance.layout.text.layout.FullBleedImageLayout
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

/**
* Glance widget showcasing Full Bleed Snap Scrolling, powered by [FakeImageGridDataRepository].
*/
class FullBleedImageAppWidget : GlanceAppWidget() {
override val sizeMode: SizeMode = SizeMode.Exact

override val previewSizeMode = SizeMode.Responsive(
setOf(
DpSize(109.dp, 115.dp)
)
)

override suspend fun provideGlance(context: Context, id: GlanceId) {
val repo = getImageGridDataRepo(id)

val initialData = withContext(Dispatchers.IO) {
repo.load(context)
}

provideContent {
val data by repo.data().collectAsState(initial = initialData)

GlanceTheme {
FullBleedImageLayout(
data = data
)
}
}
}

override suspend fun providePreview(context: Context, widgetCategory: Int) {
val repo = FakeImageGridDataRepository()

val initialData = withContext(Dispatchers.IO) {
repo.load(context)
}

provideContent {
GlanceTheme {
FullBleedImageLayout(
data = initialData.take(1)
)
}
}
}
}

/**
* Receiver for the Full Bleed Snap Scrolling widget.
*/
class FullBleedImageAppWidgetReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget = FullBleedImageAppWidget()

override fun onDeleted(context: Context, appWidgetIds: IntArray) {
val glanceAppWidgetManager = GlanceAppWidgetManager(context)
appWidgetIds.forEach { id ->
try {
val glanceId = glanceAppWidgetManager.getGlanceIdBy(id)
FakeImageGridDataRepository.cleanUp(glanceId)
} catch (e: IllegalArgumentException) {
Log.w("FullBleedImageReceiver", "Skipping cleanup for invalid AppWidget ID: $id", e)
}
}
super.onDeleted(context, appWidgetIds)
}
}
Loading
Loading