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,
                         )