diff options
| author | 2023-07-06 19:55:02 +0000 | |
|---|---|---|
| committer | 2023-07-06 19:55:02 +0000 | |
| commit | 6f5a52d7cfced7302c9d7e796a0ab53a433b128d (patch) | |
| tree | 3663087cfc41237d7331ec9b2b05e242b51deebb /java | |
| parent | 72b7c90fd2b665ae2cd84d223c565b702084d54d (diff) | |
| parent | 114b886b4aaf3341b4072f184c96cc71d4763f76 (diff) | |
Merge "Creates application-level and activity-level dagger graphs" into udc-qpr-dev
Diffstat (limited to 'java')
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 +} |