diff --git a/app/src/androidTest/java/se/ajpanton/notificationsmaster/module/OneUiCardsGridRendererTest.kt b/app/src/androidTest/java/se/ajpanton/notificationsmaster/module/OneUiCardsGridRendererTest.kt new file mode 100644 index 0000000..96fc488 --- /dev/null +++ b/app/src/androidTest/java/se/ajpanton/notificationsmaster/module/OneUiCardsGridRendererTest.kt @@ -0,0 +1,67 @@ +package se.ajpanton.notificationsmaster.module + +import android.content.Intent +import android.graphics.drawable.Icon +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test +import org.junit.runner.RunWith +import se.ajpanton.notificationsmaster.visibility.CardsGridSettings + +@RunWith(AndroidJUnit4::class) +class OneUiCardsGridRendererTest { + private val instrumentation = InstrumentationRegistry.getInstrumentation() + + @Test fun hidesStockOnlyAfterRenderingAndRestoresIt() { + val activity = instrumentation.startActivitySync( + Intent(instrumentation.targetContext, GridTestActivity::class.java) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), + ) + val renderer = OneUiCardsGridRenderer() + lateinit var root: ViewGroup + lateinit var source: FrameLayout + + instrumentation.runOnMainSync { + root = activity.window.decorView as ViewGroup + source = FrameLayout(activity) + root.addView(source, ViewGroup.LayoutParams(300, 80)) + renderer.updateSources(source, listOf(OneUiCardsGridRenderer.Source( + "test-key", + view = null, + Icon.createWithResource(activity, android.R.drawable.ic_dialog_info), + ))) + renderer.refresh(source, active = true, emptySet(), CONFIG) + } + instrumentation.waitForIdleSync() + + instrumentation.runOnMainSync { + assertEquals(0f, source.alpha) + val host = root.findViewWithTag("notifications-master:cards-grid") + assertEquals(View.VISIBLE, host.visibility) + assertEquals(1, host.childCount) + renderer.refresh(source, active = false, emptySet(), CONFIG) + } + instrumentation.waitForIdleSync() + + instrumentation.runOnMainSync { + assertEquals(1f, source.alpha) + renderer.release(source) + assertNull(root.findViewWithTag("notifications-master:cards-grid")) + root.removeView(source) + activity.finish() + } + } + + private companion object { + val CONFIG = OneUiCardsGridRenderer.Config( + CardsGridSettings(1, 5, true, true, 60), + showPills = false, + hideShelf = false, + ) + } +} diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..5a0c625 --- /dev/null +++ b/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/debug/java/se/ajpanton/notificationsmaster/module/GridTestActivity.kt b/app/src/debug/java/se/ajpanton/notificationsmaster/module/GridTestActivity.kt new file mode 100644 index 0000000..6de74f7 --- /dev/null +++ b/app/src/debug/java/se/ajpanton/notificationsmaster/module/GridTestActivity.kt @@ -0,0 +1,12 @@ +package se.ajpanton.notificationsmaster.module + +import android.app.Activity +import android.os.Bundle +import android.widget.FrameLayout + +class GridTestActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(FrameLayout(this)) + } +} diff --git a/app/src/main/java/se/ajpanton/notificationsmaster/module/OneUiCardsGridRenderer.kt b/app/src/main/java/se/ajpanton/notificationsmaster/module/OneUiCardsGridRenderer.kt index 0d9293a..9c17666 100644 --- a/app/src/main/java/se/ajpanton/notificationsmaster/module/OneUiCardsGridRenderer.kt +++ b/app/src/main/java/se/ajpanton/notificationsmaster/module/OneUiCardsGridRenderer.kt @@ -46,6 +46,17 @@ internal class OneUiCardsGridRenderer { } } + fun release(container: ViewGroup) { + val state = states.remove(container) ?: return + restoreNative(state) + state.attachListener?.let(container::removeOnAttachStateChangeListener) + state.layoutListener?.let { state.root?.removeOnLayoutChangeListener(it) } + state.host?.let { host -> + clearHost(host) + (host.parent as? ViewGroup)?.removeView(host) + } + } + private fun render(container: ViewGroup, state: State, active: Boolean) { if (!active || !container.isAttachedToWindow) { failOpen(state) @@ -139,13 +150,14 @@ internal class OneUiCardsGridRenderer { } private fun notificationIcon(root: View, source: Source, size: Int): ImageView? { - val drawable = findDrawable(source.view) + var drawable = findDrawable(source.view) ?: runCatching { val context = source.packageName?.let { root.context.createPackageContext(it, 0) } ?: root.context source.smallIcon?.loadDrawable(context) }.getOrNull() ?: return null + source.tintColor?.let { color -> drawable = drawable.mutate().apply { setTint(color) } } val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) val bounds = Rect(drawable.bounds) try { @@ -271,13 +283,21 @@ internal class OneUiCardsGridRenderer { private fun dp(view: View, value: Int) = (value * view.resources.displayMetrics.density).toInt() - private fun state(container: ViewGroup) = states.getOrPut(container, ::State) + private fun state(container: ViewGroup) = states.getOrPut(container) { + State().also { state -> + state.attachListener = object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(view: View) = Unit + override fun onViewDetachedFromWindow(view: View) = release(container) + }.also(container::addOnAttachStateChangeListener) + } + } data class Source( val key: String, val view: View?, val smallIcon: android.graphics.drawable.Icon?, val packageName: String? = null, + val tintColor: Int? = null, ) data class Config( @@ -292,6 +312,7 @@ internal class OneUiCardsGridRenderer { var host: FrameLayout? = null var root: ViewGroup? = null var layoutListener: View.OnLayoutChangeListener? = null + var attachListener: View.OnAttachStateChangeListener? = null var active = false var nativeSurface: View? = null var nativeAlpha = 1f