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

Commit b945dbd2 authored by Matt Pietal's avatar Matt Pietal Committed by Android (Google) Code Review
Browse files

Merge "Controls - Prevent action while locked" into sc-dev

parents 7534f364 d832feaa
Loading
Loading
Loading
Loading
+12 −5
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import android.service.controls.actions.CommandAction
import android.service.controls.actions.FloatAction
import android.util.Log
import android.view.HapticFeedbackConstants
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
@@ -71,7 +72,7 @@ class ControlActionCoordinatorImpl @Inject constructor(
    }

    override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
        bouncerOrRun(Action(cvh.cws.ci.controlId, {
        bouncerOrRun(createAction(cvh.cws.ci.controlId, {
            cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
            cvh.action(BooleanAction(templateId, !isChecked))
        }, true /* blockable */))
@@ -79,7 +80,7 @@ class ControlActionCoordinatorImpl @Inject constructor(

    override fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {
        val blockable = cvh.usePanel()
        bouncerOrRun(Action(cvh.cws.ci.controlId, {
        bouncerOrRun(createAction(cvh.cws.ci.controlId, {
            cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
            if (cvh.usePanel()) {
                showDialog(cvh, control.getAppIntent().getIntent())
@@ -98,13 +99,13 @@ class ControlActionCoordinatorImpl @Inject constructor(
    }

    override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) {
        bouncerOrRun(Action(cvh.cws.ci.controlId, {
        bouncerOrRun(createAction(cvh.cws.ci.controlId, {
            cvh.action(FloatAction(templateId, newValue))
        }, false /* blockable */))
    }

    override fun longPress(cvh: ControlViewHolder) {
        bouncerOrRun(Action(cvh.cws.ci.controlId, {
        bouncerOrRun(createAction(cvh.cws.ci.controlId, {
            // Long press snould only be called when there is valid control state, otherwise ignore
            cvh.cws.control?.let {
                cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
@@ -114,6 +115,7 @@ class ControlActionCoordinatorImpl @Inject constructor(
    }

    override fun runPendingAction(controlId: String) {
        if (!keyguardStateController.isUnlocked()) return
        if (pendingAction?.controlId == controlId) {
            pendingAction?.invoke()
            pendingAction = null
@@ -135,7 +137,8 @@ class ControlActionCoordinatorImpl @Inject constructor(
            false
        }

    private fun bouncerOrRun(action: Action) {
    @VisibleForTesting
    fun bouncerOrRun(action: Action) {
        if (keyguardStateController.isShowing()) {
            var closeDialog = !keyguardStateController.isUnlocked()
            if (closeDialog) {
@@ -190,6 +193,10 @@ class ControlActionCoordinatorImpl @Inject constructor(
        }
    }

    @VisibleForTesting
    fun createAction(controlId: String, f: () -> Unit, blockable: Boolean) =
        Action(controlId, f, blockable)

    inner class Action(val controlId: String, val f: () -> Unit, val blockable: Boolean) {
        fun invoke() {
            if (!blockable || shouldRunAction(controlId)) {
+126 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.ui

import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.wm.shell.TaskViewFactory
import dagger.Lazy
import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Answers
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations

@SmallTest
@RunWith(AndroidTestingRunner::class)
class ControlActionCoordinatorImplTest : SysuiTestCase() {

    @Mock
    private lateinit var uiController: ControlsUiController
    @Mock
    private lateinit var lazyUiController: Lazy<ControlsUiController>
    @Mock
    private lateinit var keyguardStateController: KeyguardStateController
    @Mock
    private lateinit var bgExecutor: DelayableExecutor
    @Mock
    private lateinit var uiExecutor: DelayableExecutor
    @Mock
    private lateinit var activityStarter: ActivityStarter
    @Mock
    private lateinit var globalActionsComponent: GlobalActionsComponent
    @Mock
    private lateinit var taskViewFactory: Optional<TaskViewFactory>
    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    private lateinit var cvh: ControlViewHolder

    companion object {
        fun <T> any(): T = Mockito.any<T>()

        private val ID = "id"
    }

    private lateinit var coordinator: ControlActionCoordinatorImpl
    private lateinit var action: ControlActionCoordinatorImpl.Action

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        coordinator = spy(ControlActionCoordinatorImpl(
            mContext,
            bgExecutor,
            uiExecutor,
            activityStarter,
            keyguardStateController,
            globalActionsComponent,
            taskViewFactory,
            getFakeBroadcastDispatcher(),
            lazyUiController
        ))

        `when`(cvh.cws.ci.controlId).thenReturn(ID)
        action = spy(coordinator.Action(ID, {}, false))
        doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean())
    }

    @Test
    fun testToggleRunsWhenUnlocked() {
        `when`(keyguardStateController.isShowing()).thenReturn(false)

        coordinator.toggle(cvh, "", true)
        verify(coordinator).bouncerOrRun(action)
        verify(action).invoke()
    }

    @Test
    fun testToggleDoesNotRunWhenLockedThenRunsWhenUnlocked() {
        `when`(keyguardStateController.isShowing()).thenReturn(true)
        `when`(keyguardStateController.isUnlocked()).thenReturn(false)

        coordinator.toggle(cvh, "", true)
        verify(coordinator).bouncerOrRun(action)
        verify(activityStarter).dismissKeyguardThenExecute(any(), any(), anyBoolean())
        verify(action, never()).invoke()

        // Simulate a refresh call from a Publisher, which will trigger a call to runPendingAction
        reset(action)
        coordinator.runPendingAction(ID)
        verify(action, never()).invoke()

        `when`(keyguardStateController.isUnlocked()).thenReturn(true)
        reset(action)
        coordinator.runPendingAction(ID)
        verify(action).invoke()
    }
}