Allow specifying components for GlanceAppWidget
Add GlanceAppWidget.getComponents to allow specifying the ComponentNames
for the action trampolines, RunCallback actions, and
GlanceRemoteViewsService. Also makes the default classes for those
components `public open` so that they can be re-used in multiple
manifest entries.
Bug: 333396676
Test: tested with multiprocess API
Relnote: "Allow users to specify the components used when translating
RemoteViews. These are specified with the GlanceComponents class"
Change-Id: Ic7e675169e131aad5d51479c7fa6bd3a1868e508
diff --git a/glance/glance-appwidget/api/current.txt b/glance/glance-appwidget/api/current.txt
index f212864..6a2e4dc 100644
--- a/glance/glance-appwidget/api/current.txt
+++ b/glance/glance-appwidget/api/current.txt
@@ -100,6 +100,11 @@
method public suspend Object? compose(android.content.Context context, long size, optional Object? state, optional android.os.Bundle appWidgetOptions, kotlin.jvm.functions.Function0<kotlin.Unit> content, kotlin.coroutines.Continuation<? super androidx.glance.appwidget.RemoteViewsCompositionResult>);
}
+ public class GlanceRemoteViewsService extends android.widget.RemoteViewsService {
+ ctor public GlanceRemoteViewsService();
+ method public android.widget.RemoteViewsService.RemoteViewsFactory onGetViewFactory(android.content.Intent? intent);
+ }
+
public final class ImageProvidersKt {
method public static androidx.glance.ImageProvider ImageProvider(android.net.Uri uri);
}
@@ -180,6 +185,19 @@
method public suspend Object? onAction(android.content.Context context, androidx.glance.GlanceId glanceId, androidx.glance.action.ActionParameters parameters, kotlin.coroutines.Continuation<? super kotlin.Unit>);
}
+ public class ActionCallbackBroadcastReceiver extends android.content.BroadcastReceiver {
+ ctor public ActionCallbackBroadcastReceiver();
+ method public void onReceive(android.content.Context? context, android.content.Intent? intent);
+ }
+
+ public class ActionTrampolineActivity extends android.app.Activity {
+ ctor public ActionTrampolineActivity();
+ }
+
+ public class InvisibleActionTrampolineActivity extends android.app.Activity {
+ ctor public InvisibleActionTrampolineActivity();
+ }
+
public final class RunCallbackActionKt {
method public static inline <reified T extends androidx.glance.appwidget.action.ActionCallback> androidx.glance.action.Action actionRunCallback(optional androidx.glance.action.ActionParameters parameters);
method public static <T extends androidx.glance.appwidget.action.ActionCallback> androidx.glance.action.Action actionRunCallback(Class<T> callbackClass, optional androidx.glance.action.ActionParameters parameters);
diff --git a/glance/glance-appwidget/api/restricted_current.txt b/glance/glance-appwidget/api/restricted_current.txt
index f212864..6a2e4dc 100644
--- a/glance/glance-appwidget/api/restricted_current.txt
+++ b/glance/glance-appwidget/api/restricted_current.txt
@@ -100,6 +100,11 @@
method public suspend Object? compose(android.content.Context context, long size, optional Object? state, optional android.os.Bundle appWidgetOptions, kotlin.jvm.functions.Function0<kotlin.Unit> content, kotlin.coroutines.Continuation<? super androidx.glance.appwidget.RemoteViewsCompositionResult>);
}
+ public class GlanceRemoteViewsService extends android.widget.RemoteViewsService {
+ ctor public GlanceRemoteViewsService();
+ method public android.widget.RemoteViewsService.RemoteViewsFactory onGetViewFactory(android.content.Intent? intent);
+ }
+
public final class ImageProvidersKt {
method public static androidx.glance.ImageProvider ImageProvider(android.net.Uri uri);
}
@@ -180,6 +185,19 @@
method public suspend Object? onAction(android.content.Context context, androidx.glance.GlanceId glanceId, androidx.glance.action.ActionParameters parameters, kotlin.coroutines.Continuation<? super kotlin.Unit>);
}
+ public class ActionCallbackBroadcastReceiver extends android.content.BroadcastReceiver {
+ ctor public ActionCallbackBroadcastReceiver();
+ method public void onReceive(android.content.Context? context, android.content.Intent? intent);
+ }
+
+ public class ActionTrampolineActivity extends android.app.Activity {
+ ctor public ActionTrampolineActivity();
+ }
+
+ public class InvisibleActionTrampolineActivity extends android.app.Activity {
+ ctor public InvisibleActionTrampolineActivity();
+ }
+
public final class RunCallbackActionKt {
method public static inline <reified T extends androidx.glance.appwidget.action.ActionCallback> androidx.glance.action.Action actionRunCallback(optional androidx.glance.action.ActionParameters parameters);
method public static <T extends androidx.glance.appwidget.action.ActionCallback> androidx.glance.action.Action actionRunCallback(Class<T> callbackClass, optional androidx.glance.action.ActionParameters parameters);
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt
index 15dc5b4..38f030f 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/AppWidgetSession.kt
@@ -184,7 +184,8 @@
layoutConfig,
layoutConfig.addLayout(root),
DpSize.Unspecified,
- receiver
+ receiver,
+ widget.getComponents(context),
)
if (shouldPublish) {
appWidgetManager.updateAppWidget(id.appWidgetId, rv)
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt
index 9b03f3c..e614389f 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceAppWidget.kt
@@ -17,6 +17,7 @@
package androidx.glance.appwidget
import android.appwidget.AppWidgetManager
+import android.content.ComponentName
import android.content.Context
import android.os.Build
import android.os.Bundle
@@ -28,6 +29,9 @@
import androidx.compose.runtime.Composable
import androidx.glance.GlanceComposable
import androidx.glance.GlanceId
+import androidx.glance.appwidget.action.ActionCallbackBroadcastReceiver
+import androidx.glance.appwidget.action.ActionTrampolineActivity
+import androidx.glance.appwidget.action.InvisibleActionTrampolineActivity
import androidx.glance.appwidget.state.getAppWidgetState
import androidx.glance.session.GlanceSessionManager
import androidx.glance.session.SessionManager
@@ -222,6 +226,13 @@
val session = getSession(glanceId.toSessionKey()) as AppWidgetSession
block(session)
}
+
+ /**
+ * Override this function to specify the components that will be used for actions and
+ * RemoteViewsService. All of the components must run in the same process.
+ */
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ open fun getComponents(context: Context) = GlanceComponents.getDefault(context)
}
@RestrictTo(Scope.LIBRARY_GROUP) data class AppWidgetId(val appWidgetId: Int) : GlanceId
@@ -266,3 +277,32 @@
"GlanceAppWidget.provideGlance"
)
}
+
+/**
+ * Specifies which components will be used as targets for action trampolines, RunCallback actions,
+ * and RemoteViewsService when creating RemoteViews. These components must all run in the same
+ * process.
+ */
+@RestrictTo(Scope.LIBRARY_GROUP)
+open class GlanceComponents(
+ open val actionTrampolineActivity: ComponentName,
+ open val invisibleActionTrampolineActivity: ComponentName,
+ open val actionCallbackBroadcastReceiver: ComponentName,
+ open val remoteViewsService: ComponentName,
+) {
+
+ companion object {
+
+ /** The default components used for GlanceAppWidget. */
+ fun getDefault(context: Context) =
+ GlanceComponents(
+ actionTrampolineActivity =
+ ComponentName(context, ActionTrampolineActivity::class.java),
+ invisibleActionTrampolineActivity =
+ ComponentName(context, InvisibleActionTrampolineActivity::class.java),
+ actionCallbackBroadcastReceiver =
+ ComponentName(context, ActionCallbackBroadcastReceiver::class.java),
+ remoteViewsService = ComponentName(context, GlanceRemoteViewsService::class.java),
+ )
+ }
+}
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceRemoteViewsService.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceRemoteViewsService.kt
index 573b01a..3e0422f 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceRemoteViewsService.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/GlanceRemoteViewsService.kt
@@ -34,9 +34,9 @@
* [RemoteViewsService] to be connected to for a remote adapter that returns RemoteViews for lazy
* lists / grids.
*/
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class GlanceRemoteViewsService : RemoteViewsService() {
- override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
+open class GlanceRemoteViewsService : RemoteViewsService() {
+ override fun onGetViewFactory(intent: Intent?): RemoteViewsFactory {
+ requireNotNull(intent) { "Intent is null" }
val appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
check(appWidgetId != -1) { "No app widget id was present in the intent" }
@@ -49,10 +49,11 @@
return GlanceRemoteViewsFactory(this, appWidgetId, viewId, sizeInfo)
}
- companion object {
- const val EXTRA_VIEW_ID = "androidx.glance.widget.extra.view_id"
- const val EXTRA_SIZE_INFO = "androidx.glance.widget.extra.size_info"
- const val TAG = "GlanceRemoteViewService"
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ internal companion object {
+ internal const val EXTRA_VIEW_ID = "androidx.glance.widget.extra.view_id"
+ internal const val EXTRA_SIZE_INFO = "androidx.glance.widget.extra.size_info"
+ internal const val TAG = "GlanceRemoteViewService"
// An in-memory store containing items to be returned via the adapter when requested.
private val InMemoryStore = RemoteCollectionItemsInMemoryStore()
@@ -227,8 +228,7 @@
*/
@Suppress("DEPRECATION")
internal fun RemoteViews.setRemoteAdapter(
- context: Context,
- appWidgetId: Int,
+ translationContext: TranslationContext,
viewId: Int,
sizeInfo: String,
items: RemoteCollectionItems
@@ -236,8 +236,11 @@
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S) {
CollectionItemsApi31Impl.setRemoteAdapter(this, viewId, items)
} else {
+ val context = translationContext.context
+ val appWidgetId = translationContext.appWidgetId
val intent =
- Intent(context, GlanceRemoteViewsService::class.java)
+ Intent()
+ .setComponent(translationContext.glanceComponents.remoteViewsService)
.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
.putExtra(GlanceRemoteViewsService.EXTRA_VIEW_ID, viewId)
.putExtra(GlanceRemoteViewsService.EXTRA_SIZE_INFO, sizeInfo)
@@ -246,7 +249,7 @@
data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
}
check(context.packageManager.resolveService(intent, 0) != null) {
- "GlanceRemoteViewsService could not be resolved, check the app manifest."
+ "${intent.component} could not be resolved, check the app manifest."
}
setRemoteAdapter(viewId, intent)
GlanceRemoteViewsService.saveItems(appWidgetId, viewId, sizeInfo, items)
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RemoteViewsTranslator.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RemoteViewsTranslator.kt
index ed0f454..4f100a4 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RemoteViewsTranslator.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/RemoteViewsTranslator.kt
@@ -70,6 +70,7 @@
rootViewIndex: Int,
layoutSize: DpSize,
actionBroadcastReceiver: ComponentName? = null,
+ glanceComponents: GlanceComponents = GlanceComponents.getDefault(context),
) =
translateComposition(
TranslationContext(
@@ -80,6 +81,7 @@
itemPosition = -1,
layoutSize = layoutSize,
actionBroadcastReceiver = actionBroadcastReceiver,
+ glanceComponents = glanceComponents,
),
element.children,
rootViewIndex,
@@ -166,7 +168,8 @@
val layoutCollectionItemId: Int = -1,
val canUseSelectableGroup: Boolean = false,
val actionTargetId: Int? = null,
- val actionBroadcastReceiver: ComponentName? = null
+ val actionBroadcastReceiver: ComponentName? = null,
+ val glanceComponents: GlanceComponents,
) {
fun nextViewId() = lastViewId.incrementAndGet()
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ActionCallbackBroadcastReceiver.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ActionCallbackBroadcastReceiver.kt
index b820300..6249896 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ActionCallbackBroadcastReceiver.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ActionCallbackBroadcastReceiver.kt
@@ -20,21 +20,25 @@
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
+import androidx.annotation.RestrictTo
import androidx.core.os.bundleOf
import androidx.glance.action.ActionParameters
import androidx.glance.action.mutableActionParametersOf
import androidx.glance.appwidget.AppWidgetId
+import androidx.glance.appwidget.TranslationContext
import androidx.glance.appwidget.goAsync
import androidx.glance.appwidget.logException
import kotlinx.coroutines.CancellationException
/** Responds to broadcasts from [RunCallbackAction] clicks by executing the associated action. */
-internal class ActionCallbackBroadcastReceiver : BroadcastReceiver() {
+open class ActionCallbackBroadcastReceiver : BroadcastReceiver() {
@Suppress("DEPRECATION")
- override fun onReceive(context: Context, intent: Intent) {
+ override fun onReceive(context: Context?, intent: Intent?) {
goAsync {
try {
+ requireNotNull(context) { "Context is null" }
+ requireNotNull(intent) { "Intent is null" }
val extras =
requireNotNull(intent.extras) {
"The intent must have action parameters extras."
@@ -72,21 +76,21 @@
}
}
- companion object {
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ internal companion object {
private const val AppWidgetId = "ActionCallbackBroadcastReceiver:appWidgetId"
private const val ExtraCallbackClassName = "ActionCallbackBroadcastReceiver:callbackClass"
private const val ExtraParameters = "ActionCallbackBroadcastReceiver:parameters"
internal fun createIntent(
- context: Context,
+ translationContext: TranslationContext,
callbackClass: Class<out ActionCallback>,
- appWidgetId: Int,
parameters: ActionParameters
) =
- Intent(context, ActionCallbackBroadcastReceiver::class.java)
- .setPackage(context.packageName)
+ Intent()
+ .setComponent(translationContext.glanceComponents.actionCallbackBroadcastReceiver)
.putExtra(ExtraCallbackClassName, callbackClass.canonicalName)
- .putExtra(AppWidgetId, appWidgetId)
+ .putExtra(AppWidgetId, translationContext.appWidgetId)
.putParameterExtras(parameters)
private fun Intent.putParameterExtras(parameters: ActionParameters): Intent {
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ActionTrampoline.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ActionTrampoline.kt
index 349855a..a4ab714 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ActionTrampoline.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ActionTrampoline.kt
@@ -49,13 +49,13 @@
type: ActionTrampolineType,
activityOptions: Bundle? = null,
): Intent {
- val target =
- if (type == ActionTrampolineType.ACTIVITY) {
- ActionTrampolineActivity::class.java
- } else {
- InvisibleActionTrampolineActivity::class.java
- }
- return Intent(translationContext.context, target).also { intent ->
+ return Intent().also { intent ->
+ intent.component =
+ if (type == ActionTrampolineType.ACTIVITY) {
+ translationContext.glanceComponents.actionTrampolineActivity
+ } else {
+ translationContext.glanceComponents.invisibleActionTrampolineActivity
+ }
intent.data = createUniqueUri(translationContext, viewId, type)
intent.putExtra(ActionTypeKey, type.name)
intent.putExtra(ActionIntentKey, this)
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ActionTrampolineActivity.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ActionTrampolineActivity.kt
index 2c32f61..3a39b04 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ActionTrampolineActivity.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ActionTrampolineActivity.kt
@@ -24,7 +24,7 @@
* This trampoline is only used for device versions before [android.os.Build.VERSION_CODES.Q].
*/
@Suppress("ForbiddenSuperClass")
-internal class ActionTrampolineActivity : Activity() {
+open class ActionTrampolineActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ApplyAction.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ApplyAction.kt
index a248d03..5a20820 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ApplyAction.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/ApplyAction.kt
@@ -149,9 +149,8 @@
translationContext.context,
0,
ActionCallbackBroadcastReceiver.createIntent(
- translationContext.context,
+ translationContext,
action.callbackClass,
- translationContext.appWidgetId,
editParams(action.parameters)
)
.apply {
@@ -263,10 +262,9 @@
}
is RunCallbackAction -> {
ActionCallbackBroadcastReceiver.createIntent(
- context = translationContext.context,
- callbackClass = action.callbackClass,
- appWidgetId = translationContext.appWidgetId,
- parameters = editParams(action.parameters)
+ translationContext,
+ action.callbackClass,
+ editParams(action.parameters)
)
.applyTrampolineIntent(
translationContext,
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/InvisibleActionTrampolineActivity.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/InvisibleActionTrampolineActivity.kt
index 0462188..aee25e7 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/InvisibleActionTrampolineActivity.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/action/InvisibleActionTrampolineActivity.kt
@@ -24,7 +24,7 @@
* that don't launch an activity. Thus not showing any UI.
*/
@Suppress("ForbiddenSuperClass")
-internal class InvisibleActionTrampolineActivity : Activity() {
+open class InvisibleActionTrampolineActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/LazyListTranslator.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/LazyListTranslator.kt
index 196630f..54da621 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/LazyListTranslator.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/LazyListTranslator.kt
@@ -98,8 +98,7 @@
}
.build()
setRemoteAdapter(
- translationContext.context,
- translationContext.appWidgetId,
+ translationContext,
viewDef.mainViewId,
translationContext.layoutSize.toSizeString(),
items
diff --git a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/LazyVerticalGridTranslator.kt b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/LazyVerticalGridTranslator.kt
index 44185c4..b9e0fa7 100644
--- a/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/LazyVerticalGridTranslator.kt
+++ b/glance/glance-appwidget/src/main/java/androidx/glance/appwidget/translators/LazyVerticalGridTranslator.kt
@@ -110,8 +110,7 @@
}
.build()
setRemoteAdapter(
- translationContext.context,
- translationContext.appWidgetId,
+ translationContext,
viewDef.mainViewId,
translationContext.layoutSize.toSizeString(),
items
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/ActionCallbackBroadcastReceiverTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/ActionCallbackBroadcastReceiverTest.kt
index 128e60a..9d9c681 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/ActionCallbackBroadcastReceiverTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/ActionCallbackBroadcastReceiverTest.kt
@@ -55,26 +55,28 @@
}
private fun createPendingIntent(parameters: ActionParameters, viewId: Int): PendingIntent {
+ val translationContext =
+ TranslationContext(
+ context,
+ appWidgetId = 1,
+ isRtl = false,
+ layoutConfiguration = LayoutConfiguration.create(context, 1),
+ itemPosition = -1,
+ isLazyCollectionDescendant = false,
+ glanceComponents = GlanceComponents.getDefault(context),
+ )
return PendingIntent.getBroadcast(
context,
0,
ActionCallbackBroadcastReceiver.createIntent(
- context = context,
+ translationContext = translationContext,
callbackClass = ActionCallback::class.java,
- appWidgetId = 1,
parameters = parameters
)
.apply {
data =
createUniqueUri(
- TranslationContext(
- context,
- appWidgetId = 1,
- isRtl = false,
- layoutConfiguration = LayoutConfiguration.create(context, 1),
- itemPosition = -1,
- isLazyCollectionDescendant = false,
- ),
+ translationContext,
viewId = viewId,
type = ActionTrampolineType.CALLBACK,
)