Migrate Controls{ProviderSelector,Favoriting,Editing}Activities
This moves the activities to the new Predictive Back dispatch mechanism.
Added unit tests for each (note: used a latch to synchronize the test and ui threads)
Bug: 255355028
Test: Passed atest <testname>
Change-Id: I775f636f8679abfd50a595dd56fb8fa3f23e0fef
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 2f5b5f4..9ee160c 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -114,6 +114,7 @@
"androidx.dynamicanimation_dynamicanimation",
"androidx-constraintlayout_constraintlayout",
"androidx.exifinterface_exifinterface",
+ "androidx.test.ext.junit",
"com.google.android.material_material",
"kotlinx_coroutines_android",
"kotlinx_coroutines",
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index 5e8ce6d..b11103a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -20,11 +20,14 @@
import android.content.ComponentName
import android.content.Intent
import android.os.Bundle
+import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.view.ViewStub
import android.widget.Button
import android.widget.TextView
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
import androidx.activity.ComponentActivity
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
@@ -42,7 +45,7 @@
/**
* Activity for rearranging and removing controls for a given structure
*/
-class ControlsEditingActivity @Inject constructor(
+open class ControlsEditingActivity @Inject constructor(
private val controller: ControlsControllerImpl,
private val broadcastDispatcher: BroadcastDispatcher,
private val customIconCache: CustomIconCache,
@@ -50,8 +53,9 @@
) : ComponentActivity() {
companion object {
+ private const val DEBUG = false
private const val TAG = "ControlsEditingActivity"
- private const val EXTRA_STRUCTURE = ControlsFavoritingActivity.EXTRA_STRUCTURE
+ const val EXTRA_STRUCTURE = ControlsFavoritingActivity.EXTRA_STRUCTURE
private val SUBTITLE_ID = R.string.controls_favorite_rearrange
private val EMPTY_TEXT_ID = R.string.controls_favorite_removed
}
@@ -73,6 +77,13 @@
}
}
+ private val mOnBackInvokedCallback = OnBackInvokedCallback {
+ if (DEBUG) {
+ Log.d(TAG, "Predictive Back dispatcher called mOnBackInvokedCallback")
+ }
+ onBackPressed()
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -94,11 +105,22 @@
setUpList()
currentUserTracker.startTracking()
+
+ if (DEBUG) {
+ Log.d(TAG, "Registered onBackInvokedCallback")
+ }
+ onBackInvokedDispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback)
}
override fun onStop() {
super.onStop()
currentUserTracker.stopTracking()
+
+ if (DEBUG) {
+ Log.d(TAG, "Unregistered onBackInvokedCallback")
+ }
+ onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mOnBackInvokedCallback)
}
override fun onBackPressed() {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index be572c5..9b2a728 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -24,6 +24,7 @@
import android.content.res.Configuration
import android.os.Bundle
import android.text.TextUtils
+import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
@@ -32,6 +33,8 @@
import android.widget.FrameLayout
import android.widget.TextView
import android.widget.Toast
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
import androidx.activity.ComponentActivity
import androidx.viewpager2.widget.ViewPager2
import com.android.systemui.Prefs
@@ -50,7 +53,7 @@
import java.util.function.Consumer
import javax.inject.Inject
-class ControlsFavoritingActivity @Inject constructor(
+open class ControlsFavoritingActivity @Inject constructor(
@Main private val executor: Executor,
private val controller: ControlsControllerImpl,
private val listingController: ControlsListingController,
@@ -59,6 +62,7 @@
) : ComponentActivity() {
companion object {
+ private const val DEBUG = false
private const val TAG = "ControlsFavoritingActivity"
// If provided and no structure is available, use as the title
@@ -67,7 +71,7 @@
// If provided, show this structure page first
const val EXTRA_STRUCTURE = "extra_structure"
const val EXTRA_SINGLE_STRUCTURE = "extra_single_structure"
- internal const val EXTRA_FROM_PROVIDER_SELECTOR = "extra_from_provider_selector"
+ const val EXTRA_FROM_PROVIDER_SELECTOR = "extra_from_provider_selector"
private const val TOOLTIP_PREFS_KEY = Prefs.Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT
private const val TOOLTIP_MAX_SHOWN = 2
}
@@ -102,6 +106,13 @@
}
}
+ private val mOnBackInvokedCallback = OnBackInvokedCallback {
+ if (DEBUG) {
+ Log.d(TAG, "Predictive Back dispatcher called mOnBackInvokedCallback")
+ }
+ onBackPressed()
+ }
+
private val listingCallback = object : ControlsListingController.ControlsListingCallback {
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
@@ -346,13 +357,19 @@
override fun onPause() {
super.onPause()
mTooltipManager?.hide(false)
- }
+ }
override fun onStart() {
super.onStart()
listingController.addCallback(listingCallback)
currentUserTracker.startTracking()
+
+ if (DEBUG) {
+ Log.d(TAG, "Registered onBackInvokedCallback")
+ }
+ onBackInvokedDispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback)
}
override fun onResume() {
@@ -365,13 +382,19 @@
loadControls()
isPagerLoaded = true
}
- }
+ }
override fun onStop() {
super.onStop()
listingController.removeCallback(listingCallback)
currentUserTracker.stopTracking()
+
+ if (DEBUG) {
+ Log.d(TAG, "Unregistered onBackInvokedCallback")
+ }
+ onBackInvokedDispatcher.unregisterOnBackInvokedCallback(
+ mOnBackInvokedCallback)
}
override fun onConfigurationChanged(newConfig: Configuration) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index b26615f..47690a7 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -20,16 +20,18 @@
import android.content.ComponentName
import android.content.Intent
import android.os.Bundle
+import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewStub
import android.widget.Button
import android.widget.TextView
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
import androidx.activity.ComponentActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
-import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsController
@@ -44,7 +46,7 @@
/**
* Activity to select an application to favorite the [Control] provided by them.
*/
-class ControlsProviderSelectorActivity @Inject constructor(
+open class ControlsProviderSelectorActivity @Inject constructor(
@Main private val executor: Executor,
@Background private val backExecutor: Executor,
private val listingController: ControlsListingController,
@@ -54,6 +56,7 @@
) : ComponentActivity() {
companion object {
+ private const val DEBUG = false
private const val TAG = "ControlsProviderSelectorActivity"
const val BACK_SHOULD_EXIT = "back_should_exit"
}
@@ -70,6 +73,13 @@
}
}
+ private val mOnBackInvokedCallback = OnBackInvokedCallback {
+ if (DEBUG) {
+ Log.d(TAG, "Predictive Back dispatcher called mOnBackInvokedCallback")
+ }
+ onBackPressed()
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -141,11 +151,22 @@
}
})
}
+
+ if (DEBUG) {
+ Log.d(TAG, "Registered onBackInvokedCallback")
+ }
+ onBackInvokedDispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback)
}
override fun onStop() {
super.onStop()
currentUserTracker.stopTracking()
+
+ if (DEBUG) {
+ Log.d(TAG, "Unregistered onBackInvokedCallback")
+ }
+ onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mOnBackInvokedCallback)
}
/**
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 1b404a8..8abdc87 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -93,6 +93,21 @@
android:excludeFromRecents="true"
/>
+ <activity android:name="com.android.systemui.controls.management.ControlsEditingActivityTest$TestableControlsEditingActivity"
+ android:exported="false"
+ android:excludeFromRecents="true"
+ />
+
+ <activity android:name="com.android.systemui.controls.management.ControlsFavoritingActivityTest$TestableControlsFavoritingActivity"
+ android:exported="false"
+ android:excludeFromRecents="true"
+ />
+
+ <activity android:name="com.android.systemui.controls.management.ControlsProviderSelectorActivityTest$TestableControlsProviderSelectorActivity"
+ android:exported="false"
+ android:excludeFromRecents="true"
+ />
+
<activity android:name="com.android.systemui.screenshot.ScrollViewActivity"
android:exported="false" />
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
new file mode 100644
index 0000000..0b72a68
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
@@ -0,0 +1,112 @@
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.intercepting.SingleActivityFactory
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.controls.CustomIconCache
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.controls.ui.ControlsUiController
+import java.util.concurrent.CountDownLatch
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ControlsEditingActivityTest : SysuiTestCase() {
+ @Mock lateinit var controller: ControlsControllerImpl
+
+ @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
+
+ @Mock lateinit var customIconCache: CustomIconCache
+
+ @Mock lateinit var uiController: ControlsUiController
+
+ private lateinit var controlsEditingActivity: ControlsEditingActivity_Factory
+ private var latch: CountDownLatch = CountDownLatch(1)
+
+ @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
+ @Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback>
+
+ @Rule
+ @JvmField
+ var activityRule =
+ ActivityTestRule(
+ object :
+ SingleActivityFactory<TestableControlsEditingActivity>(
+ TestableControlsEditingActivity::class.java
+ ) {
+ override fun create(intent: Intent?): TestableControlsEditingActivity {
+ return TestableControlsEditingActivity(
+ controller,
+ broadcastDispatcher,
+ customIconCache,
+ uiController,
+ mockDispatcher,
+ latch
+ )
+ }
+ },
+ false,
+ false
+ )
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ val intent = Intent()
+ intent.putExtra(ControlsEditingActivity.EXTRA_STRUCTURE, "TestTitle")
+ val cname = ComponentName("TestPackageName", "TestClassName")
+ intent.putExtra(Intent.EXTRA_COMPONENT_NAME, cname)
+ activityRule.launchActivity(intent)
+ }
+
+ @Test
+ fun testBackCallbackRegistrationAndUnregistration() {
+ // 1. ensure that launching the activity results in it registering a callback
+ verify(mockDispatcher)
+ .registerOnBackInvokedCallback(
+ ArgumentMatchers.eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
+ captureCallback.capture()
+ )
+ activityRule.finishActivity()
+ latch.await() // ensure activity is finished
+ // 2. ensure that when the activity is finished, it unregisters the same callback
+ verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value)
+ }
+
+ public class TestableControlsEditingActivity(
+ private val controller: ControlsControllerImpl,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val customIconCache: CustomIconCache,
+ private val uiController: ControlsUiController,
+ private val mockDispatcher: OnBackInvokedDispatcher,
+ private val latch: CountDownLatch
+ ) : ControlsEditingActivity(controller, broadcastDispatcher, customIconCache, uiController) {
+ override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
+ return mockDispatcher
+ }
+
+ override fun onStop() {
+ super.onStop()
+ // ensures that test runner thread does not proceed until ui thread is done
+ latch.countDown()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
new file mode 100644
index 0000000..4b0f7e6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -0,0 +1,122 @@
+package com.android.systemui.controls.management
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.intercepting.SingleActivityFactory
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.qualifiers.Main
+import com.google.common.util.concurrent.MoreExecutors
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ControlsFavoritingActivityTest : SysuiTestCase() {
+ @Main private val executor: Executor = MoreExecutors.directExecutor()
+
+ @Mock lateinit var controller: ControlsControllerImpl
+
+ @Mock lateinit var listingController: ControlsListingController
+
+ @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
+
+ @Mock lateinit var uiController: ControlsUiController
+
+ private lateinit var controlsFavoritingActivity: ControlsFavoritingActivity_Factory
+ private var latch: CountDownLatch = CountDownLatch(1)
+
+ @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
+ @Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback>
+
+ @Rule
+ @JvmField
+ var activityRule =
+ ActivityTestRule(
+ object :
+ SingleActivityFactory<TestableControlsFavoritingActivity>(
+ TestableControlsFavoritingActivity::class.java
+ ) {
+ override fun create(intent: Intent?): TestableControlsFavoritingActivity {
+ return TestableControlsFavoritingActivity(
+ executor,
+ controller,
+ listingController,
+ broadcastDispatcher,
+ uiController,
+ mockDispatcher,
+ latch
+ )
+ }
+ },
+ false,
+ false
+ )
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ val intent = Intent()
+ intent.putExtra(ControlsFavoritingActivity.EXTRA_FROM_PROVIDER_SELECTOR, true)
+ activityRule.launchActivity(intent)
+ }
+
+ @Test
+ fun testBackCallbackRegistrationAndUnregistration() {
+ // 1. ensure that launching the activity results in it registering a callback
+ verify(mockDispatcher)
+ .registerOnBackInvokedCallback(
+ ArgumentMatchers.eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
+ captureCallback.capture()
+ )
+ activityRule.finishActivity()
+ latch.await() // ensure activity is finished
+ // 2. ensure that when the activity is finished, it unregisters the same callback
+ verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value)
+ }
+
+ public class TestableControlsFavoritingActivity(
+ executor: Executor,
+ controller: ControlsControllerImpl,
+ listingController: ControlsListingController,
+ broadcastDispatcher: BroadcastDispatcher,
+ uiController: ControlsUiController,
+ private val mockDispatcher: OnBackInvokedDispatcher,
+ private val latch: CountDownLatch
+ ) :
+ ControlsFavoritingActivity(
+ executor,
+ controller,
+ listingController,
+ broadcastDispatcher,
+ uiController
+ ) {
+ override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
+ return mockDispatcher
+ }
+
+ override fun onStop() {
+ super.onStop()
+ // ensures that test runner thread does not proceed until ui thread is done
+ latch.countDown()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
new file mode 100644
index 0000000..acc6222
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsProviderSelectorActivityTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 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.controls.management
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.intercepting.SingleActivityFactory
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.google.common.util.concurrent.MoreExecutors
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ControlsProviderSelectorActivityTest : SysuiTestCase() {
+ @Main private val executor: Executor = MoreExecutors.directExecutor()
+
+ @Background private val backExecutor: Executor = MoreExecutors.directExecutor()
+
+ @Mock lateinit var listingController: ControlsListingController
+
+ @Mock lateinit var controlsController: ControlsController
+
+ @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
+
+ @Mock lateinit var uiController: ControlsUiController
+
+ private lateinit var controlsProviderSelectorActivity: ControlsProviderSelectorActivity_Factory
+ private var latch: CountDownLatch = CountDownLatch(1)
+
+ @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
+ @Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback>
+
+ @Rule
+ @JvmField
+ var activityRule =
+ ActivityTestRule(
+ object :
+ SingleActivityFactory<TestableControlsProviderSelectorActivity>(
+ TestableControlsProviderSelectorActivity::class.java
+ ) {
+ override fun create(intent: Intent?): TestableControlsProviderSelectorActivity {
+ return TestableControlsProviderSelectorActivity(
+ executor,
+ backExecutor,
+ listingController,
+ controlsController,
+ broadcastDispatcher,
+ uiController,
+ mockDispatcher,
+ latch
+ )
+ }
+ },
+ false,
+ false
+ )
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ val intent = Intent()
+ intent.putExtra(ControlsProviderSelectorActivity.BACK_SHOULD_EXIT, true)
+ activityRule.launchActivity(intent)
+ }
+
+ @Test
+ fun testBackCallbackRegistrationAndUnregistration() {
+ // 1. ensure that launching the activity results in it registering a callback
+ verify(mockDispatcher)
+ .registerOnBackInvokedCallback(
+ ArgumentMatchers.eq(OnBackInvokedDispatcher.PRIORITY_DEFAULT),
+ captureCallback.capture()
+ )
+ activityRule.finishActivity()
+ latch.await() // ensure activity is finished
+ // 2. ensure that when the activity is finished, it unregisters the same callback
+ verify(mockDispatcher).unregisterOnBackInvokedCallback(captureCallback.value)
+ }
+
+ public class TestableControlsProviderSelectorActivity(
+ executor: Executor,
+ backExecutor: Executor,
+ listingController: ControlsListingController,
+ controlsController: ControlsController,
+ broadcastDispatcher: BroadcastDispatcher,
+ uiController: ControlsUiController,
+ private val mockDispatcher: OnBackInvokedDispatcher,
+ private val latch: CountDownLatch
+ ) :
+ ControlsProviderSelectorActivity(
+ executor,
+ backExecutor,
+ listingController,
+ controlsController,
+ broadcastDispatcher,
+ uiController
+ ) {
+ override fun getOnBackInvokedDispatcher(): OnBackInvokedDispatcher {
+ return mockDispatcher
+ }
+
+ override fun onStop() {
+ super.onStop()
+ // ensures that test runner thread does not proceed until ui thread is done
+ latch.countDown()
+ }
+ }
+}