Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 52c10f48 authored by Fabian Kozynski's avatar Fabian Kozynski
Browse files

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
parent 5cb3c557
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -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
     */
+17 −4
Original line number Diff line number Diff line
@@ -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}")
        }
    }
}
+217 −5
Original line number Diff line number Diff line
@@ -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
                        }
                    }
                }
            )
    }
}