summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
Diffstat (limited to 'java')
-rw-r--r--java/src/com/android/intentresolver/ApplicationComponentOwner.kt15
-rw-r--r--java/src/com/android/intentresolver/ChooserActivity.java3
-rw-r--r--java/src/com/android/intentresolver/ChooserActivityReEnabler.kt3
-rw-r--r--java/src/com/android/intentresolver/IntentForwarderActivity.java7
-rw-r--r--java/src/com/android/intentresolver/IntentResolverAppComponentFactory.kt75
-rw-r--r--java/src/com/android/intentresolver/IntentResolverApplication.kt28
-rw-r--r--java/src/com/android/intentresolver/ResolverActivity.java3
-rw-r--r--java/src/com/android/intentresolver/dagger/ActivityBinderModule.kt30
-rw-r--r--java/src/com/android/intentresolver/dagger/ActivityModule.kt6
-rw-r--r--java/src/com/android/intentresolver/dagger/ActivityScope.kt5
-rw-r--r--java/src/com/android/intentresolver/dagger/ActivitySubComponent.kt18
-rw-r--r--java/src/com/android/intentresolver/dagger/App.kt5
-rw-r--r--java/src/com/android/intentresolver/dagger/ApplicationComponent.kt23
-rw-r--r--java/src/com/android/intentresolver/dagger/ApplicationModule.kt23
-rw-r--r--java/src/com/android/intentresolver/dagger/ReceiverBinderModule.kt18
-rw-r--r--java/tests/AndroidManifest.xml7
-rw-r--r--java/tests/src/com/android/intentresolver/TestApplication.kt25
17 files changed, 289 insertions, 5 deletions
diff --git a/java/src/com/android/intentresolver/ApplicationComponentOwner.kt b/java/src/com/android/intentresolver/ApplicationComponentOwner.kt
new file mode 100644
index 00000000..fb39814c
--- /dev/null
+++ b/java/src/com/android/intentresolver/ApplicationComponentOwner.kt
@@ -0,0 +1,15 @@
+package com.android.intentresolver
+
+import com.android.intentresolver.dagger.ApplicationComponent
+
+/**
+ * Interface that should be implemented by the [Application][android.app.Application] object as the
+ * owner of the [ApplicationComponent].
+ */
+interface ApplicationComponentOwner {
+ /**
+ * Invokes the given [action] when the [ApplicationComponent] has been created. If it has
+ * already been created, then it invokes [action] immediately.
+ */
+ fun doWhenApplicationComponentReady(action: (ApplicationComponent) -> Unit)
+}
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index 63ac6435..c615d388 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -118,6 +118,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
+import javax.inject.Inject;
+
/**
* The Chooser Activity handles intent resolution specifically for sharing intents -
* for example, as generated by {@see android.content.Intent#createChooser(Intent, CharSequence)}.
@@ -225,6 +227,7 @@ public class ChooserActivity extends ResolverActivity implements
private boolean mExcludeSharedText = false;
+ @Inject
public ChooserActivity() {}
@Override
diff --git a/java/src/com/android/intentresolver/ChooserActivityReEnabler.kt b/java/src/com/android/intentresolver/ChooserActivityReEnabler.kt
index 3236c1be..8c2b3d0d 100644
--- a/java/src/com/android/intentresolver/ChooserActivityReEnabler.kt
+++ b/java/src/com/android/intentresolver/ChooserActivityReEnabler.kt
@@ -5,11 +5,12 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
+import javax.inject.Inject
/**
* Ensures that the unbundled version of [ChooserActivity] does not get stuck in a disabled state.
*/
-class ChooserActivityReEnabler : BroadcastReceiver() {
+class ChooserActivityReEnabler @Inject constructor() : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
diff --git a/java/src/com/android/intentresolver/IntentForwarderActivity.java b/java/src/com/android/intentresolver/IntentForwarderActivity.java
index 5e8945f1..d69a6c71 100644
--- a/java/src/com/android/intentresolver/IntentForwarderActivity.java
+++ b/java/src/com/android/intentresolver/IntentForwarderActivity.java
@@ -57,6 +57,8 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import javax.inject.Inject;
+
/**
* This is used in conjunction with
* {@link DevicePolicyManager#addCrossProfileIntentFilter} to enable intents to
@@ -84,6 +86,11 @@ public class IntentForwarderActivity extends Activity {
private MetricsLogger mMetricsLogger;
protected ExecutorService mExecutorService;
+ @Inject
+ public IntentForwarderActivity() {
+ super();
+ }
+
@Override
protected void onDestroy() {
super.onDestroy();
diff --git a/java/src/com/android/intentresolver/IntentResolverAppComponentFactory.kt b/java/src/com/android/intentresolver/IntentResolverAppComponentFactory.kt
new file mode 100644
index 00000000..eef49ec4
--- /dev/null
+++ b/java/src/com/android/intentresolver/IntentResolverAppComponentFactory.kt
@@ -0,0 +1,75 @@
+package com.android.intentresolver
+
+import android.app.Activity
+import android.app.Application
+import android.content.BroadcastReceiver
+import android.content.Intent
+import android.util.Log
+import androidx.core.app.AppComponentFactory
+import com.android.intentresolver.dagger.ActivitySubComponent
+import javax.inject.Inject
+import javax.inject.Provider
+
+/** [AppComponentFactory] that performs dagger injection on Android components. */
+class IntentResolverAppComponentFactory : AppComponentFactory() {
+
+ @set:Inject lateinit var activitySubComponentBuilder: Provider<ActivitySubComponent.Builder>
+ @set:Inject
+ lateinit var receivers: Map<Class<*>, @JvmSuppressWildcards Provider<BroadcastReceiver>>
+
+ override fun instantiateApplicationCompat(cl: ClassLoader, className: String): Application {
+ val app = super.instantiateApplicationCompat(cl, className)
+ if (app !is ApplicationComponentOwner) {
+ throw RuntimeException("App must be ApplicationComponentOwner")
+ }
+ app.doWhenApplicationComponentReady { it.inject(this) }
+ return app
+ }
+
+ override fun instantiateActivityCompat(
+ cl: ClassLoader,
+ className: String,
+ intent: Intent?,
+ ): Activity {
+ return runCatching {
+ val activities = activitySubComponentBuilder.get().build().activities()
+ instantiate(className, activities)
+ }
+ .onFailure {
+ if (it is UninitializedPropertyAccessException) {
+ // This should never happen but if it did it would cause errors that could
+ // be very difficult to identify, so we log it out of an abundance of
+ // caution.
+ Log.e(TAG, "Tried to instantiate $className before AppComponent", it)
+ }
+ }
+ .getOrNull()
+ ?: super.instantiateActivityCompat(cl, className, intent)
+ }
+
+ override fun instantiateReceiverCompat(
+ cl: ClassLoader,
+ className: String,
+ intent: Intent?,
+ ): BroadcastReceiver {
+ return instantiate(className, receivers)
+ ?: super.instantiateReceiverCompat(cl, className, intent)
+ }
+
+ private fun <T> instantiate(className: String, providers: Map<Class<*>, Provider<T>>): T? {
+ return runCatching { providers[Class.forName(className)]?.get() }
+ .onFailure {
+ if (it is UninitializedPropertyAccessException) {
+ // This should never happen but if it did it would cause errors that could
+ // be very difficult to identify, so we log it out of an abundance of
+ // caution.
+ Log.e(TAG, "Tried to instantiate $className before AppComponent", it)
+ }
+ }
+ .getOrNull()
+ }
+
+ companion object {
+ private const val TAG = "IRAppComponentFactory"
+ }
+}
diff --git a/java/src/com/android/intentresolver/IntentResolverApplication.kt b/java/src/com/android/intentresolver/IntentResolverApplication.kt
new file mode 100644
index 00000000..73d15f3c
--- /dev/null
+++ b/java/src/com/android/intentresolver/IntentResolverApplication.kt
@@ -0,0 +1,28 @@
+package com.android.intentresolver
+
+import android.app.Application
+import com.android.intentresolver.dagger.ApplicationComponent
+import com.android.intentresolver.dagger.DaggerApplicationComponent
+
+/** [Application] that maintains the [ApplicationComponent]. */
+class IntentResolverApplication : Application(), ApplicationComponentOwner {
+
+ private lateinit var applicationComponent: ApplicationComponent
+
+ private val pendingDaggerActions = mutableSetOf<(ApplicationComponent) -> Unit>()
+
+ override fun onCreate() {
+ super.onCreate()
+ applicationComponent = DaggerApplicationComponent.builder().application(this).build()
+ pendingDaggerActions.forEach { it.invoke(applicationComponent) }
+ pendingDaggerActions.clear()
+ }
+
+ override fun doWhenApplicationComponentReady(action: (ApplicationComponent) -> Unit) {
+ if (this::applicationComponent.isInitialized) {
+ action.invoke(applicationComponent)
+ } else {
+ pendingDaggerActions.add(action)
+ }
+ }
+}
diff --git a/java/src/com/android/intentresolver/ResolverActivity.java b/java/src/com/android/intentresolver/ResolverActivity.java
index 57871532..dfc5a021 100644
--- a/java/src/com/android/intentresolver/ResolverActivity.java
+++ b/java/src/com/android/intentresolver/ResolverActivity.java
@@ -125,6 +125,8 @@ import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
+import javax.inject.Inject;
+
/**
* This is a copy of ResolverActivity to support IntentResolver's ChooserActivity. This code is
* *not* the resolver that is actually triggered by the system right now (you want
@@ -135,6 +137,7 @@ import java.util.function.Supplier;
public class ResolverActivity extends FragmentActivity implements
ResolverListAdapter.ResolverListCommunicator {
+ @Inject
public ResolverActivity() {
mIsIntentPicker = getClass().equals(ResolverActivity.class);
}
diff --git a/java/src/com/android/intentresolver/dagger/ActivityBinderModule.kt b/java/src/com/android/intentresolver/dagger/ActivityBinderModule.kt
new file mode 100644
index 00000000..59b08c2c
--- /dev/null
+++ b/java/src/com/android/intentresolver/dagger/ActivityBinderModule.kt
@@ -0,0 +1,30 @@
+package com.android.intentresolver.dagger
+
+import android.app.Activity
+import com.android.intentresolver.ChooserActivity
+import com.android.intentresolver.IntentForwarderActivity
+import com.android.intentresolver.ResolverActivity
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+/** Injection instructions for injectable [Activities][Activity]. */
+@Module
+interface ActivityBinderModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(ChooserActivity::class)
+ fun bindChooserActivity(activity: ChooserActivity): Activity
+
+ @Binds
+ @IntoMap
+ @ClassKey(ResolverActivity::class)
+ fun bindResolverActivity(activity: ResolverActivity): Activity
+
+ @Binds
+ @IntoMap
+ @ClassKey(IntentForwarderActivity::class)
+ fun bindIntentForwarderActivity(activity: IntentForwarderActivity): Activity
+}
diff --git a/java/src/com/android/intentresolver/dagger/ActivityModule.kt b/java/src/com/android/intentresolver/dagger/ActivityModule.kt
new file mode 100644
index 00000000..a1b7551a
--- /dev/null
+++ b/java/src/com/android/intentresolver/dagger/ActivityModule.kt
@@ -0,0 +1,6 @@
+package com.android.intentresolver.dagger
+
+import dagger.Module
+
+/** Injections for the [ActivitySubComponent] */
+@Module(includes = [ActivityBinderModule::class]) interface ActivityModule
diff --git a/java/src/com/android/intentresolver/dagger/ActivityScope.kt b/java/src/com/android/intentresolver/dagger/ActivityScope.kt
new file mode 100644
index 00000000..dc3a8bef
--- /dev/null
+++ b/java/src/com/android/intentresolver/dagger/ActivityScope.kt
@@ -0,0 +1,5 @@
+package com.android.intentresolver.dagger
+
+import javax.inject.Scope
+
+@MustBeDocumented @Retention(AnnotationRetention.RUNTIME) @Scope annotation class ActivityScope
diff --git a/java/src/com/android/intentresolver/dagger/ActivitySubComponent.kt b/java/src/com/android/intentresolver/dagger/ActivitySubComponent.kt
new file mode 100644
index 00000000..092b9088
--- /dev/null
+++ b/java/src/com/android/intentresolver/dagger/ActivitySubComponent.kt
@@ -0,0 +1,18 @@
+package com.android.intentresolver.dagger
+
+import android.app.Activity
+import dagger.Subcomponent
+import javax.inject.Provider
+
+/** Subcomponent for injections across the life of an Activity. */
+@ActivityScope
+@Subcomponent(modules = [ActivityModule::class])
+interface ActivitySubComponent {
+
+ @Subcomponent.Builder
+ interface Builder {
+ fun build(): ActivitySubComponent
+ }
+
+ fun activities(): Map<Class<*>, @JvmSuppressWildcards Provider<Activity>>
+}
diff --git a/java/src/com/android/intentresolver/dagger/App.kt b/java/src/com/android/intentresolver/dagger/App.kt
new file mode 100644
index 00000000..a16272e8
--- /dev/null
+++ b/java/src/com/android/intentresolver/dagger/App.kt
@@ -0,0 +1,5 @@
+package com.android.intentresolver.dagger
+
+import javax.inject.Qualifier
+
+@MustBeDocumented @Retention(AnnotationRetention.RUNTIME) @Qualifier annotation class App
diff --git a/java/src/com/android/intentresolver/dagger/ApplicationComponent.kt b/java/src/com/android/intentresolver/dagger/ApplicationComponent.kt
new file mode 100644
index 00000000..ed337e8b
--- /dev/null
+++ b/java/src/com/android/intentresolver/dagger/ApplicationComponent.kt
@@ -0,0 +1,23 @@
+package com.android.intentresolver.dagger
+
+import android.app.Application
+import com.android.intentresolver.IntentResolverAppComponentFactory
+import dagger.BindsInstance
+import dagger.Component
+import javax.inject.Singleton
+
+/** Top level component for injections across the life of the process. */
+@Singleton
+@Component(modules = [ApplicationModule::class])
+interface ApplicationComponent {
+
+ @Component.Builder
+ interface Builder {
+
+ @BindsInstance fun application(application: Application): Builder
+
+ fun build(): ApplicationComponent
+ }
+
+ fun inject(appComponentFactory: IntentResolverAppComponentFactory)
+}
diff --git a/java/src/com/android/intentresolver/dagger/ApplicationModule.kt b/java/src/com/android/intentresolver/dagger/ApplicationModule.kt
new file mode 100644
index 00000000..f9285c25
--- /dev/null
+++ b/java/src/com/android/intentresolver/dagger/ApplicationModule.kt
@@ -0,0 +1,23 @@
+package com.android.intentresolver.dagger
+
+import android.app.Application
+import android.content.Context
+import dagger.Module
+import dagger.Provides
+import javax.inject.Singleton
+
+/** Injections for the [ApplicationComponent] */
+@Module(
+ subcomponents = [ActivitySubComponent::class],
+ includes = [ReceiverBinderModule::class],
+)
+abstract class ApplicationModule {
+
+ companion object {
+ @Provides
+ @Singleton
+ @App
+ fun provideApplicationContext(application: Application): Context =
+ application.applicationContext
+ }
+}
diff --git a/java/src/com/android/intentresolver/dagger/ReceiverBinderModule.kt b/java/src/com/android/intentresolver/dagger/ReceiverBinderModule.kt
new file mode 100644
index 00000000..1f587f18
--- /dev/null
+++ b/java/src/com/android/intentresolver/dagger/ReceiverBinderModule.kt
@@ -0,0 +1,18 @@
+package com.android.intentresolver.dagger
+
+import android.content.BroadcastReceiver
+import com.android.intentresolver.ChooserActivityReEnabler
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+/** Injection instructions for injectable [BroadcastReceivers][BroadcastReceiver] */
+@Module
+interface ReceiverBinderModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(ChooserActivityReEnabler::class)
+ fun bindChooserActivityReEnabler(receiver: ChooserActivityReEnabler): BroadcastReceiver
+}
diff --git a/java/tests/AndroidManifest.xml b/java/tests/AndroidManifest.xml
index 05830c4c..b397db5f 100644
--- a/java/tests/AndroidManifest.xml
+++ b/java/tests/AndroidManifest.xml
@@ -15,7 +15,8 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.intentresolver.tests">
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.intentresolver.tests">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
@@ -25,7 +26,9 @@
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
- <application android:name="com.android.intentresolver.TestApplication">
+ <application
+ tools:replace="android:name"
+ android:name="com.android.intentresolver.TestApplication" >
<uses-library android:name="android.test.runner" />
<activity android:name="com.android.intentresolver.ChooserWrapperActivity" />
<activity android:name="com.android.intentresolver.ResolverWrapperActivity" />
diff --git a/java/tests/src/com/android/intentresolver/TestApplication.kt b/java/tests/src/com/android/intentresolver/TestApplication.kt
index 849cfbab..f0761fbd 100644
--- a/java/tests/src/com/android/intentresolver/TestApplication.kt
+++ b/java/tests/src/com/android/intentresolver/TestApplication.kt
@@ -19,9 +19,30 @@ package com.android.intentresolver
import android.app.Application
import android.content.Context
import android.os.UserHandle
+import com.android.intentresolver.dagger.ApplicationComponent
+import com.android.intentresolver.dagger.DaggerApplicationComponent
-class TestApplication : Application() {
+class TestApplication : Application(), ApplicationComponentOwner {
+
+ private lateinit var applicationComponent: ApplicationComponent
+
+ private val pendingDaggerActions = mutableSetOf<(ApplicationComponent) -> Unit>()
+
+ override fun onCreate() {
+ super.onCreate()
+ applicationComponent = DaggerApplicationComponent.builder().application(this).build()
+ pendingDaggerActions.forEach { it.invoke(applicationComponent) }
+ pendingDaggerActions.clear()
+ }
+
+ override fun doWhenApplicationComponentReady(action: (ApplicationComponent) -> Unit) {
+ if (this::applicationComponent.isInitialized) {
+ action.invoke(applicationComponent)
+ } else {
+ pendingDaggerActions.add(action)
+ }
+ }
// return the current context as a work profile doesn't really exist in these tests
override fun createContextAsUser(user: UserHandle, flags: Int): Context = this
-} \ No newline at end of file
+}