Merge "Migrate Controls{ProviderSelector,Favoriting,Editing}Activities" into tm-qpr-dev

This commit is contained in:
Rahul Banerjee
2022-11-09 01:08:55 +00:00
committed by Android (Google) Code Review
8 changed files with 468 additions and 8 deletions

View File

@@ -114,6 +114,7 @@ android_library {
"androidx.dynamicanimation_dynamicanimation",
"androidx-constraintlayout_constraintlayout",
"androidx.exifinterface_exifinterface",
"androidx.test.ext.junit",
"com.google.android.material_material",
"kotlin-reflect",
"kotlinx_coroutines_android",

View File

@@ -20,11 +20,14 @@ import android.app.ActivityOptions
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 @@ import javax.inject.Inject
/**
* 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 @@ class ControlsEditingActivity @Inject constructor(
) : 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 @@ class ControlsEditingActivity @Inject constructor(
}
}
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 @@ class ControlsEditingActivity @Inject constructor(
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() {

View File

@@ -24,6 +24,7 @@ import android.content.Intent
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.Button
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.concurrent.Executor
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 @@ class ControlsFavoritingActivity @Inject constructor(
) : 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 @@ class ControlsFavoritingActivity @Inject constructor(
// 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 @@ class ControlsFavoritingActivity @Inject constructor(
}
}
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 @@ class ControlsFavoritingActivity @Inject constructor(
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 @@ class ControlsFavoritingActivity @Inject constructor(
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) {

View File

@@ -20,16 +20,18 @@ import android.app.ActivityOptions
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 @@ import javax.inject.Inject
/**
* 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 @@ class ControlsProviderSelectorActivity @Inject constructor(
) : 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 @@ class ControlsProviderSelectorActivity @Inject constructor(
}
}
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 @@ class ControlsProviderSelectorActivity @Inject constructor(
}
})
}
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)
}
/**

View File

@@ -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" />

View File

@@ -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()
}
}
}

View File

@@ -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()
}
}
}

View File

@@ -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()
}
}
}