Fix BlurUtils crash

We need to abort the operation if the given SurfaceControl is invalid.

Test: atest BlurUtilsTest
Fixes: 148110676
Change-Id: Iaed655e06d48f4d7dd6ee2df25838188e24813c6
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index 083fbc9..ab69d47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -22,6 +22,7 @@
 import android.util.MathUtils
 import android.view.SurfaceControl
 import android.view.ViewRootImpl
+import androidx.annotation.VisibleForTesting
 import com.android.internal.util.IndentingPrintWriter
 import com.android.systemui.DumpController
 import com.android.systemui.Dumpable
@@ -33,7 +34,7 @@
 import javax.inject.Singleton
 
 @Singleton
-class BlurUtils @Inject constructor(
+open class BlurUtils @Inject constructor(
     @Main private val resources: Resources,
     val dumpController: DumpController
 ) : Dumpable {
@@ -63,22 +64,28 @@
      * @param radius blur radius in pixels.
      */
     fun applyBlur(viewRootImpl: ViewRootImpl?, radius: Int) {
-        if (viewRootImpl == null || !supportsBlursOnWindows()) {
+        if (viewRootImpl == null || !viewRootImpl.surfaceControl.isValid ||
+                !supportsBlursOnWindows()) {
             return
         }
-        SurfaceControl.Transaction().use {
+        createTransaction().use {
             it.setBackgroundBlurRadius(viewRootImpl.surfaceControl, radius)
             it.apply()
         }
     }
 
+    @VisibleForTesting
+    open fun createTransaction(): SurfaceControl.Transaction {
+        return SurfaceControl.Transaction()
+    }
+
     /**
      * If this device can render blurs.
      *
      * @see android.view.SurfaceControl.Transaction#setBackgroundBlurRadius(SurfaceControl, int)
      * @return {@code true} when supported.
      */
-    fun supportsBlursOnWindows(): Boolean {
+    open fun supportsBlursOnWindows(): Boolean {
         return blurSysProp && ActivityManager.isHighEndGfx()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt
new file mode 100644
index 0000000..c180a88
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 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.systemui.statusbar
+
+import android.content.res.Resources
+import android.view.SurfaceControl
+import android.view.ViewRootImpl
+import androidx.test.filters.SmallTest
+import com.android.systemui.DumpController
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class BlurUtilsTest : SysuiTestCase() {
+
+    @Mock lateinit var resources: Resources
+    @Mock lateinit var dumpController: DumpController
+    @Mock lateinit var transaction: SurfaceControl.Transaction
+    lateinit var blurUtils: BlurUtils
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        blurUtils = TestableBlurUtils()
+    }
+
+    @Test
+    fun testApplyBlur_noViewRoot_doesntCrash() {
+        blurUtils.applyBlur(null /* viewRootImple */, 10 /* radius */)
+    }
+
+    @Test
+    fun testApplyBlur_invalidSurfaceControl() {
+        val surfaceControl = mock(SurfaceControl::class.java)
+        val viewRootImpl = mock(ViewRootImpl::class.java)
+        `when`(viewRootImpl.surfaceControl).thenReturn(surfaceControl)
+        blurUtils.applyBlur(viewRootImpl, 10 /* radius */)
+    }
+
+    @Test
+    fun testApplyBlur_success() {
+        val radius = 10
+        val surfaceControl = mock(SurfaceControl::class.java)
+        val viewRootImpl = mock(ViewRootImpl::class.java)
+        `when`(viewRootImpl.surfaceControl).thenReturn(surfaceControl)
+        `when`(surfaceControl.isValid).thenReturn(true)
+        blurUtils.applyBlur(viewRootImpl, radius)
+        verify(transaction).setBackgroundBlurRadius(eq(surfaceControl), eq(radius))
+        verify(transaction).apply()
+    }
+
+    inner class TestableBlurUtils() : BlurUtils(resources, dumpController) {
+        override fun supportsBlursOnWindows(): Boolean {
+            return true
+        }
+
+        override fun createTransaction(): SurfaceControl.Transaction {
+            return transaction
+        }
+    }
+}
\ No newline at end of file