Fixing LauncherAppCompoent not available for isolated context
In those cases, creating a new LauncherAppCompoent and storing it in
the application context
Bug: 372180905
Test: atest LauncherComponentProviderTest
Flag: EXEMPT bugfix
Change-Id: Ibf517a23801138b1fa18e8e4a7adbdedb994365b
diff --git a/src/com/android/launcher3/dagger/LauncherComponentProvider.kt b/src/com/android/launcher3/dagger/LauncherComponentProvider.kt
new file mode 100644
index 0000000..5015e54
--- /dev/null
+++ b/src/com/android/launcher3/dagger/LauncherComponentProvider.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.dagger
+
+import android.content.Context
+import android.view.LayoutInflater
+import com.android.launcher3.LauncherApplication
+
+/**
+ * Utility class to extract LauncherAppComponent from a context.
+ *
+ * If the context doesn't provide LauncherAppComponent by default, it creates a new one and
+ * associate it with that context
+ */
+object LauncherComponentProvider {
+
+ @JvmStatic
+ fun get(c: Context): LauncherAppComponent {
+ val app = c.applicationContext
+ if (app is LauncherApplication) return app.appComponent
+
+ val inflater = LayoutInflater.from(app)
+ val existingFilter = inflater.filter
+ if (existingFilter is Holder) return existingFilter.component
+
+ // Create a new component
+ return Holder(
+ DaggerLauncherAppComponent.builder().appContext(app).build()
+ as LauncherAppComponent,
+ existingFilter,
+ )
+ .apply { inflater.filter = this }
+ .component
+ }
+
+ private data class Holder(
+ val component: LauncherAppComponent,
+ private val filter: LayoutInflater.Filter?,
+ ) : LayoutInflater.Filter {
+
+ override fun onLoadClass(clazz: Class<*>?) = filter?.onLoadClass(clazz) ?: true
+ }
+}
diff --git a/src/com/android/launcher3/util/DaggerSingletonObject.java b/src/com/android/launcher3/util/DaggerSingletonObject.java
index febe6af..a245761 100644
--- a/src/com/android/launcher3/util/DaggerSingletonObject.java
+++ b/src/com/android/launcher3/util/DaggerSingletonObject.java
@@ -18,8 +18,8 @@
import android.content.Context;
-import com.android.launcher3.LauncherApplication;
import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherComponentProvider;
import java.util.function.Function;
@@ -37,8 +37,6 @@
}
public T get(Context context) {
- LauncherAppComponent component =
- ((LauncherApplication) context.getApplicationContext()).getAppComponent();
- return mFunction.apply(component);
+ return mFunction.apply(LauncherComponentProvider.get(context));
}
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/dagger/LauncherComponentProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/dagger/LauncherComponentProviderTest.kt
new file mode 100644
index 0000000..9255877
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/dagger/LauncherComponentProviderTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.dagger
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.view.ContextThemeWrapper
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.R
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
+import com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNotSame
+import org.junit.Assert.assertSame
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class LauncherComponentProviderTest {
+
+ val app: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun `returns same component as Launcher application`() {
+ val c = SandboxModelContext()
+ assertSame(c.appComponent, LauncherComponentProvider.get(c))
+ assertNotSame(LauncherComponentProvider.get(c), LauncherComponentProvider.get(app))
+ }
+
+ @Test
+ fun `returns same component for isolated context`() {
+ val c = IsolatedContext()
+
+ // Same component is returned for multiple calls, irrespective of the wrappers
+ assertNotNull(LauncherComponentProvider.get(c))
+ assertSame(
+ LauncherComponentProvider.get(c),
+ LauncherComponentProvider.get(ContextThemeWrapper(c, R.style.LauncherTheme)),
+ )
+
+ // Different than main application
+ assertNotSame(LauncherComponentProvider.get(c), LauncherComponentProvider.get(app))
+ }
+
+ @Test
+ fun `different components for different isolated context`() {
+ val c1 = IsolatedContext()
+ val c2 = IsolatedContext()
+
+ assertNotNull(LauncherComponentProvider.get(c1))
+ assertNotNull(LauncherComponentProvider.get(c2))
+ assertNotSame(LauncherComponentProvider.get(c1), LauncherComponentProvider.get(c2))
+ }
+
+ inner class IsolatedContext : ContextWrapper(app.createPackageContext(TEST_PACKAGE, 0)) {
+
+ override fun getApplicationContext(): Context = this
+ }
+}