Pass Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS

Add an EXTRA for passing the value of this setting to the activities
when the TaskView is started. That way they can respect it when showing
content.

Also, make sure that we create the pending intent for the correct user

Test: atest ControlsUiControllerImplTest
Test: manual with test app
Fixes: 259953724
Change-Id: I44bb8191f8412eb098c3cc654dea2e8badd6fabd
This commit is contained in:
Fabian Kozynski
2022-11-28 16:03:12 -05:00
parent 5cb3c55774
commit 52c10f48d0
3 changed files with 246 additions and 9 deletions

View File

@@ -68,6 +68,18 @@ public abstract class ControlsProviderService extends Service {
public static final String META_DATA_PANEL_ACTIVITY =
"android.service.controls.META_DATA_PANEL_ACTIVITY";
/**
* Boolean extra containing the value of
* {@link android.provider.Settings.Secure#LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS}.
*
* This is passed with the intent when the panel specified by {@link #META_DATA_PANEL_ACTIVITY}
* is launched.
*
* @hide
*/
public static final String EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS =
"android.service.controls.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS";
/**
* @hide
*/

View File

@@ -28,6 +28,7 @@ import android.content.Intent
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.service.controls.Control
import android.service.controls.ControlsProviderService
import android.util.Log
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
@@ -48,6 +49,7 @@ import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.ControlsSettingsRepository
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.StructureInfo
@@ -96,6 +98,7 @@ class ControlsUiControllerImpl @Inject constructor (
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
private val taskViewFactory: Optional<TaskViewFactory>,
private val controlsSettingsRepository: ControlsSettingsRepository,
dumpManager: DumpManager
) : ControlsUiController, Dumpable {
@@ -354,7 +357,6 @@ class ControlsUiControllerImpl @Inject constructor (
} else {
items[0]
}
maybeUpdateSelectedItem(selectionItem)
createControlsSpaceFrame()
@@ -374,11 +376,20 @@ class ControlsUiControllerImpl @Inject constructor (
}
private fun createPanelView(componentName: ComponentName) {
val pendingIntent = PendingIntent.getActivity(
val setting = controlsSettingsRepository
.allowActionOnTrivialControlsInLockscreen.value
val pendingIntent = PendingIntent.getActivityAsUser(
context,
0,
Intent().setComponent(componentName),
PendingIntent.FLAG_IMMUTABLE
Intent()
.setComponent(componentName)
.putExtra(
ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
setting
),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
null,
userTracker.userHandle
)
parent.requireViewById<View>(R.id.controls_scroll_view).visibility = View.GONE
@@ -698,6 +709,8 @@ class ControlsUiControllerImpl @Inject constructor (
println("hidden: $hidden")
println("selectedItem: $selectedItem")
println("lastSelections: $lastSelections")
println("setting: ${controlsSettingsRepository
.allowActionOnTrivialControlsInLockscreen.value}")
}
}
}

View File

@@ -16,15 +16,26 @@
package com.android.systemui.controls.ui
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.ServiceInfo
import android.os.UserHandle
import android.service.controls.ControlsProviderService
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.FakeControlsSettingsRepository
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.management.ControlsListingController
@@ -38,19 +49,26 @@ import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.time.FakeSystemClock
import com.android.wm.shell.TaskView
import com.android.wm.shell.TaskViewFactory
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
import java.util.Optional
import java.util.function.Consumer
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyString
import org.mockito.Mockito.mock
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -70,9 +88,9 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
@Mock lateinit var userFileManager: UserFileManager
@Mock lateinit var userTracker: UserTracker
@Mock lateinit var taskViewFactory: TaskViewFactory
@Mock lateinit var activityContext: Context
@Mock lateinit var dumpManager: DumpManager
val sharedPreferences = FakeSharedPreferences()
lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
var uiExecutor = FakeExecutor(FakeSystemClock())
var bgExecutor = FakeExecutor(FakeSystemClock())
@@ -83,6 +101,17 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
fun setup() {
MockitoAnnotations.initMocks(this)
controlsSettingsRepository = FakeControlsSettingsRepository()
// This way, it won't be cloned every time `LayoutInflater.fromContext` is called, but we
// need to clone it once so we don't modify the original one.
mContext.addMockSystemService(
Context.LAYOUT_INFLATER_SERVICE,
mContext.baseContext
.getSystemService(LayoutInflater::class.java)!!
.cloneInContext(mContext)
)
parent = FrameLayout(mContext)
underTest =
@@ -100,6 +129,7 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
userFileManager,
userTracker,
Optional.of(taskViewFactory),
controlsSettingsRepository,
dumpManager
)
`when`(
@@ -113,11 +143,12 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
`when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
.thenReturn(sharedPreferences)
`when`(userTracker.userId).thenReturn(0)
`when`(userTracker.userHandle).thenReturn(UserHandle.of(0))
}
@Test
fun testGetPreferredStructure() {
val structureInfo = mock(StructureInfo::class.java)
val structureInfo = mock<StructureInfo>()
underTest.getPreferredSelectedItem(listOf(structureInfo))
verify(userFileManager)
.getSharedPreferences(
@@ -189,14 +220,195 @@ class ControlsUiControllerImplTest : SysuiTestCase() {
@Test
fun testPanelDoesNotRefreshControls() {
val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
setUpPanel(panel)
underTest.show(parent, {}, context)
verify(controlsController, never()).refreshStatus(any(), any())
}
@Test
fun testPanelCallsTaskViewFactoryCreate() {
mockLayoutInflater()
val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
val serviceInfo = setUpPanel(panel)
underTest.show(parent, {}, context)
val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
verify(controlsListingController).addCallback(capture(captor))
captor.value.onServicesUpdated(listOf(serviceInfo))
FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
verify(taskViewFactory).create(eq(context), eq(uiExecutor), any())
}
@Test
fun testPanelControllerStartActivityWithCorrectArguments() {
mockLayoutInflater()
controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
val serviceInfo = setUpPanel(panel)
underTest.show(parent, {}, context)
val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
verify(controlsListingController).addCallback(capture(captor))
captor.value.onServicesUpdated(listOf(serviceInfo))
FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
val pendingIntent = verifyPanelCreatedAndStartTaskView()
with(pendingIntent) {
assertThat(isActivity).isTrue()
assertThat(intent.component).isEqualTo(serviceInfo.panelActivity)
assertThat(
intent.getBooleanExtra(
ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
false
)
)
.isTrue()
}
}
@Test
fun testPendingIntentExtrasAreModified() {
mockLayoutInflater()
controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
val serviceInfo = setUpPanel(panel)
underTest.show(parent, {}, context)
val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
verify(controlsListingController).addCallback(capture(captor))
captor.value.onServicesUpdated(listOf(serviceInfo))
FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
val pendingIntent = verifyPanelCreatedAndStartTaskView()
assertThat(
pendingIntent.intent.getBooleanExtra(
ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
false
)
)
.isTrue()
underTest.hide()
clearInvocations(controlsListingController, taskViewFactory)
controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(false)
underTest.show(parent, {}, context)
verify(controlsListingController).addCallback(capture(captor))
captor.value.onServicesUpdated(listOf(serviceInfo))
FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
val newPendingIntent = verifyPanelCreatedAndStartTaskView()
assertThat(
newPendingIntent.intent.getBooleanExtra(
ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
false
)
)
.isFalse()
}
private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
val activity = ComponentName("pkg", "activity")
sharedPreferences
.edit()
.putString("controls_component", panel.componentName.flattenToString())
.putString("controls_structure", panel.appName.toString())
.putBoolean("controls_is_panel", true)
.commit()
return ControlsServiceInfo(panel.componentName, panel.appName, activity)
}
underTest.show(parent, {}, activityContext)
verify(controlsController, never()).refreshStatus(any(), any())
private fun verifyPanelCreatedAndStartTaskView(): PendingIntent {
val taskViewConsumerCaptor = argumentCaptor<Consumer<TaskView>>()
verify(taskViewFactory).create(eq(context), eq(uiExecutor), capture(taskViewConsumerCaptor))
val taskView: TaskView = mock {
`when`(this.post(any())).thenAnswer {
uiExecutor.execute(it.arguments[0] as Runnable)
true
}
}
// calls PanelTaskViewController#launchTaskView
taskViewConsumerCaptor.value.accept(taskView)
val listenerCaptor = argumentCaptor<TaskView.Listener>()
verify(taskView).setListener(any(), capture(listenerCaptor))
listenerCaptor.value.onInitialized()
FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
val pendingIntentCaptor = argumentCaptor<PendingIntent>()
verify(taskView).startActivity(capture(pendingIntentCaptor), any(), any(), any())
return pendingIntentCaptor.value
}
private fun ControlsServiceInfo(
componentName: ComponentName,
label: CharSequence,
panelComponentName: ComponentName? = null
): ControlsServiceInfo {
val serviceInfo =
ServiceInfo().apply {
applicationInfo = ApplicationInfo()
packageName = componentName.packageName
name = componentName.className
}
return spy(ControlsServiceInfo(mContext, serviceInfo)).apply {
`when`(loadLabel()).thenReturn(label)
`when`(loadIcon()).thenReturn(mock())
`when`(panelActivity).thenReturn(panelComponentName)
}
}
private fun mockLayoutInflater() {
LayoutInflater.from(context)
.setPrivateFactory(
object : LayoutInflater.Factory2 {
override fun onCreateView(
view: View?,
name: String,
context: Context,
attrs: AttributeSet
): View? {
return onCreateView(name, context, attrs)
}
override fun onCreateView(
name: String,
context: Context,
attrs: AttributeSet
): View? {
if (FrameLayout::class.java.simpleName.equals(name)) {
val mock: FrameLayout = mock {
`when`(this.context).thenReturn(context)
`when`(this.id).thenReturn(R.id.controls_panel)
`when`(this.requireViewById<View>(any())).thenCallRealMethod()
`when`(this.findViewById<View>(R.id.controls_panel))
.thenReturn(this)
`when`(this.post(any())).thenAnswer {
uiExecutor.execute(it.arguments[0] as Runnable)
true
}
}
return mock
} else {
return null
}
}
}
)
}
}