1 /*
<lambda>null2 * Copyright 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.glance.appwidget.demos
18
19 import android.appwidget.AppWidgetHostView
20 import android.appwidget.AppWidgetProviderInfo
21 import android.content.ComponentName
22 import android.content.pm.ActivityInfo
23 import android.os.Bundle
24 import androidx.activity.ComponentActivity
25 import androidx.activity.compose.setContent
26 import androidx.compose.foundation.clickable
27 import androidx.compose.foundation.layout.Box
28 import androidx.compose.foundation.layout.Column
29 import androidx.compose.foundation.layout.fillMaxHeight
30 import androidx.compose.foundation.layout.fillMaxSize
31 import androidx.compose.foundation.layout.fillMaxWidth
32 import androidx.compose.foundation.layout.padding
33 import androidx.compose.foundation.lazy.LazyColumn
34 import androidx.compose.material.Text
35 import androidx.compose.runtime.Composable
36 import androidx.compose.runtime.collectAsState
37 import androidx.compose.runtime.getValue
38 import androidx.compose.runtime.mutableStateOf
39 import androidx.compose.runtime.remember
40 import androidx.compose.runtime.setValue
41 import androidx.compose.ui.Modifier
42 import androidx.compose.ui.platform.LocalContext
43 import androidx.compose.ui.unit.DpSize
44 import androidx.compose.ui.unit.dp
45 import androidx.compose.ui.viewinterop.AndroidView
46 import androidx.glance.ExperimentalGlanceApi
47 import androidx.glance.appwidget.GlanceAppWidget
48 import androidx.glance.appwidget.UnmanagedSessionReceiver
49 import androidx.glance.appwidget.runComposition
50 import kotlinx.coroutines.Dispatchers
51
52 /**
53 * Activity that displays Glance widgets using `GlanceAppWidget.runComposition` to output
54 * RemoteViews without having a bound widget.
55 */
56 class SimpleWidgetViewer : ComponentActivity() {
57 override fun onCreate(savedInstanceState: Bundle?) {
58 super.onCreate(savedInstanceState)
59 setContent {
60 val widgets =
61 listOf(
62 ActionAppWidget(),
63 BackgroundTintWidget(),
64 ButtonsWidget(),
65 CompoundButtonAppWidget(),
66 DefaultColorsAppWidget(),
67 DefaultStateAppWidget(),
68 ErrorUiAppWidget(),
69 ExactAppWidget(),
70 FontDemoWidget(),
71 ImageAppWidget(),
72 ProgressIndicatorAppWidget(),
73 RemoteViewsWidget(),
74 ResizingAppWidget(),
75 ResponsiveAppWidget(),
76 RippleAppWidget(),
77 ScrollableAppWidget(),
78 TypographyDemoAppWidget(),
79 VerticalGridAppWidget(),
80 )
81 var selectedWidget by remember { mutableStateOf(widgets.random()) }
82 Column(Modifier.fillMaxSize()) {
83 LazyColumn(modifier = Modifier.fillMaxWidth().fillMaxHeight(0.3F)) {
84 widgets.forEach { widget ->
85 item {
86 Text(
87 text = widget::class.simpleName.orEmpty(),
88 modifier = Modifier.clickable { selectedWidget = widget },
89 )
90 }
91 }
92 }
93 Box(Modifier.fillMaxWidth().fillMaxHeight(0.7F).padding(8.dp)) {
94 WidgetView(selectedWidget, DpSize(500.dp, 500.dp))
95 }
96 }
97 }
98 }
99 }
100
101 @OptIn(ExperimentalGlanceApi::class)
102 @Composable
WidgetViewnull103 fun WidgetView(widget: GlanceAppWidget, size: DpSize = DpSize(200.dp, 200.dp)) {
104 val context = LocalContext.current
105 val remoteViews by
106 remember(widget, size) {
107 widget.runComposition(
108 context = context,
109 sizes = listOf(size),
110 lambdaReceiver =
111 ComponentName(context, CustomUnmanagedSessionReceiver::class.java),
112 )
113 }
114 .collectAsState(null, Dispatchers.Default)
115 AndroidView(
116 factory = {
117 // Using an AWHV is necessary for ListView support, and to properly select a RemoteViews
118 // from a multi-size RemoteViews. If the RemoteViews has only one size and does not
119 // contain lazy list items, a FrameLayout works fine.
120 AppWidgetHostView(context).apply { setFakeAppWidget() }
121 },
122 modifier = Modifier.fillMaxSize(),
123 update = { view -> view.updateAppWidget(remoteViews) },
124 )
125 }
126
127 /**
128 * The hostView requires an AppWidgetProviderInfo to work in certain OS versions. This method uses
129 * reflection to set a fake provider info.
130 */
AppWidgetHostViewnull131 private fun AppWidgetHostView.setFakeAppWidget() {
132 val context = context
133 val info =
134 AppWidgetProviderInfo().apply {
135 initialLayout = androidx.glance.appwidget.R.layout.glance_default_loading_layout
136 }
137 try {
138 val activityInfo =
139 ActivityInfo().apply {
140 applicationInfo = context.applicationInfo
141 packageName = context.packageName
142 labelRes = applicationInfo.labelRes
143 }
144
145 info::class.java.getDeclaredField("providerInfo").run {
146 isAccessible = true
147 set(info, activityInfo)
148 }
149
150 this::class.java.getDeclaredField("mInfo").apply {
151 isAccessible = true
152 set(this@setFakeAppWidget, info)
153 }
154 } catch (e: Exception) {
155 throw IllegalStateException("Couldn't set fake provider", e)
156 }
157 }
158
159 class CustomUnmanagedSessionReceiver : UnmanagedSessionReceiver()
160