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

Commit 4c048327 authored by Matt Pietal's avatar Matt Pietal
Browse files

Controls Visibility

Start to move controls visibility logic into the ControlsComponent,
allowing multiple locations to ask about the state of controls.

Bug: 179782498
Test: atest GlobalActionsDialog ControlsComponentTest DeviceControlsTileTest

Change-Id: I09ce096c05e8b8826bd7baa479872cc7a2feaad6
parent ce7bd347
Loading
Loading
Loading
Loading
+73 −3
Original line number Diff line number Diff line
@@ -16,10 +16,19 @@

package com.android.systemui.controls.dagger

import android.content.ContentResolver
import android.content.Context
import android.database.ContentObserver
import android.provider.Settings
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.settings.SecureSettings
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
@@ -28,15 +37,43 @@ import javax.inject.Inject
 * Pseudo-component to inject into classes outside `com.android.systemui.controls`.
 *
 * If `featureEnabled` is false, all the optionals should be empty. The controllers will only be
 * instantiated if `featureEnabled` is true.
 * instantiated if `featureEnabled` is true. Can also be queried for the availability of controls.
 */
@SysUISingleton
class ControlsComponent @Inject constructor(
    @ControlsFeatureEnabled private val featureEnabled: Boolean,
    private val context: Context,
    private val lazyControlsController: Lazy<ControlsController>,
    private val lazyControlsUiController: Lazy<ControlsUiController>,
    private val lazyControlsListingController: Lazy<ControlsListingController>
    private val lazyControlsListingController: Lazy<ControlsListingController>,
    private val lockPatternUtils: LockPatternUtils,
    private val keyguardStateController: KeyguardStateController,
    private val userTracker: UserTracker,
    private val secureSettings: SecureSettings
) {

    private val contentResolver: ContentResolver
        get() = context.contentResolver

    private var canShowWhileLockedSetting = false

    val showWhileLockedObserver = object : ContentObserver(null) {
        override fun onChange(selfChange: Boolean) {
            updateShowWhileLocked()
        }
    }

    init {
        if (featureEnabled) {
            secureSettings.registerContentObserver(
                Settings.Secure.getUriFor(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT),
                false, /* notifyForDescendants */
                showWhileLockedObserver
            )
            updateShowWhileLocked()
        }
    }

    fun getControlsController(): Optional<ControlsController> {
        return if (featureEnabled) Optional.of(lazyControlsController.get()) else Optional.empty()
    }
@@ -52,4 +89,37 @@ class ControlsComponent @Inject constructor(
            Optional.empty()
        }
    }

    /**
     * @return true if controls are feature-enabled and have available services to serve controls
     */
    fun isEnabled() = featureEnabled && lazyControlsController.get().available

    /**
     * Returns one of 3 states:
     * * AVAILABLE - Controls can be made visible
     * * AVAILABLE_AFTER_UNLOCK - Controls can be made visible only after device unlock
     * * UNAVAILABLE - Controls are not enabled
     */
    fun getVisibility(): Visibility {
        if (!isEnabled()) return Visibility.UNAVAILABLE
        if (lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
                == STRONG_AUTH_REQUIRED_AFTER_BOOT) {
            return Visibility.AVAILABLE_AFTER_UNLOCK
        }
        if (!canShowWhileLockedSetting && !keyguardStateController.isUnlocked()) {
            return Visibility.AVAILABLE_AFTER_UNLOCK
        }

        return Visibility.AVAILABLE
    }

    private fun updateShowWhileLocked() {
        canShowWhileLockedSetting = secureSettings.getInt(
            Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT, 0) != 0
    }

    enum class Visibility {
        AVAILABLE, AVAILABLE_AFTER_UNLOCK, UNAVAILABLE
    }
}
+19 −15
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOM
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;

import android.animation.Animator;
@@ -250,6 +252,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
    private final IWindowManager mIWindowManager;
    private final Executor mBackgroundExecutor;
    private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>();
    private ControlsComponent mControlsComponent;
    private Optional<ControlsController> mControlsControllerOptional;
    private final RingerModeTracker mRingerModeTracker;
    private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
@@ -338,6 +341,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
        mSysuiColorExtractor = colorExtractor;
        mStatusBarService = statusBarService;
        mNotificationShadeWindowController = notificationShadeWindowController;
        mControlsComponent = controlsComponent;
        mControlsUiControllerOptional = controlsComponent.getControlsUiController();
        mIWindowManager = iWindowManager;
        mBackgroundExecutor = backgroundExecutor;
@@ -387,7 +391,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
                    if (mDialog.mWalletViewController != null) {
                        mDialog.mWalletViewController.onDeviceLockStateChanged(!unlocked);
                    }
                    if (!mDialog.isShowingControls() && shouldShowControls()) {
                    if (!mDialog.isShowingControls()
                            && mControlsComponent.getVisibility() == AVAILABLE) {
                        mDialog.showControls(mControlsUiControllerOptional.get());
                    }
                    if (unlocked) {
@@ -397,14 +402,15 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
            }
        });

        if (controlsComponent.getControlsListingController().isPresent()) {
            controlsComponent.getControlsListingController().get()
        if (mControlsComponent.getControlsListingController().isPresent()) {
            mControlsComponent.getControlsListingController().get()
                    .addCallback(list -> {
                        mControlsServiceInfos = list;
                        // This callback may occur after the dialog has been shown. If so, add
                        // controls into the already visible space or show the lock msg if needed.
                        if (mDialog != null) {
                            if (!mDialog.isShowingControls() && shouldShowControls()) {
                            if (!mDialog.isShowingControls()
                                    && mControlsComponent.getVisibility() == AVAILABLE) {
                                mDialog.showControls(mControlsUiControllerOptional.get());
                            } else if (shouldShowLockMessage(mDialog)) {
                                mDialog.showLockMessage();
@@ -704,7 +710,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,

        mDepthController.setShowingHomeControls(true);
        ControlsUiController uiController = null;
        if (mControlsUiControllerOptional.isPresent() && shouldShowControls()) {
        if (mControlsComponent.getVisibility() == AVAILABLE) {
            uiController = mControlsUiControllerOptional.get();
        }
        ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter,
@@ -2687,26 +2693,24 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
        return isPanelDebugModeEnabled(context);
    }

    private boolean shouldShowControls() {
        boolean showOnLockScreen = mShowLockScreenCardsAndControls && mLockPatternUtils
                .getStrongAuthForUser(getCurrentUser().id) != STRONG_AUTH_REQUIRED_AFTER_BOOT;
        return controlsAvailable()
                && (mKeyguardStateController.isUnlocked() || showOnLockScreen);
    }

    private boolean controlsAvailable() {
        return mDeviceProvisioned
                && mControlsUiControllerOptional.isPresent()
                && mControlsUiControllerOptional.get().getAvailable()
                && mControlsComponent.isEnabled()
                && !mControlsServiceInfos.isEmpty();
    }

    private boolean shouldShowLockMessage(ActionsDialog dialog) {
        return mControlsComponent.getVisibility() == AVAILABLE_AFTER_UNLOCK
                || isWalletAvailableAfterUnlock(dialog);
    }

    // Temporary while we move items out of the power menu
    private boolean isWalletAvailableAfterUnlock(ActionsDialog dialog) {
        boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id)
                == STRONG_AUTH_REQUIRED_AFTER_BOOT;
        return !mKeyguardStateController.isUnlocked()
                && (!mShowLockScreenCardsAndControls || isLockedAfterBoot)
                && (controlsAvailable() || dialog.isWalletViewAvailable());
                && dialog.isWalletViewAvailable();
    }

    private void onPowerMenuLockScreenSettingsChanged() {
+3 −2
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import com.android.internal.logging.MetricsLogger
import com.android.systemui.R
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsDialog
import com.android.systemui.dagger.qualifiers.Background
@@ -91,7 +92,7 @@ class DeviceControlsTile @Inject constructor(
    override fun isAvailable(): Boolean {
        return featureFlags.isKeyguardLayoutEnabled &&
                controlsLockscreen &&
                controlsComponent.getControlsUiController().isPresent
                controlsComponent.getVisibility() != UNAVAILABLE
    }

    override fun newTileState(): QSTile.State {
+31 −33
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.phone;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;

import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON;
import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK;
@@ -183,6 +184,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
    private ControlsComponent mControlsComponent;
    private int mLockScreenMode;
    private BroadcastDispatcher mBroadcastDispatcher;
    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;

    public KeyguardBottomAreaView(Context context) {
        this(context, null);
@@ -295,7 +297,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
        filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
        getContext().registerReceiverAsUser(mDevicePolicyReceiver,
                UserHandle.ALL, filter, null, null);
        Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback);
        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
        mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
        mKeyguardStateController.addCallback(this);
    }

@@ -307,7 +310,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
        mRightExtension.destroy();
        mLeftExtension.destroy();
        getContext().unregisterReceiver(mDevicePolicyReceiver);
        Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback);
        mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
    }

    private void initAccessibility() {
@@ -410,12 +413,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
    }

    private void updateLeftAffordanceIcon() {
        if (mDozing) {
            mAltLeftButton.setVisibility(GONE);
        } else if (mAltLeftButton.getDrawable() != null) {
            mAltLeftButton.setVisibility(VISIBLE);
        }

        if (!mShowLeftAffordance || mDozing) {
            mLeftAffordanceView.setVisibility(GONE);
            return;
@@ -430,6 +427,14 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
        mLeftAffordanceView.setContentDescription(state.contentDescription);
    }

    private void updateControlsVisibility() {
        if (mDozing || mControlsComponent.getVisibility() != AVAILABLE) {
            mAltLeftButton.setVisibility(GONE);
        } else {
            mAltLeftButton.setVisibility(VISIBLE);
        }
    }

    public boolean isLeftVoiceAssist() {
        return mLeftIsVoiceAssist;
    }
@@ -769,6 +774,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL

        updateCameraVisibility();
        updateLeftAffordanceIcon();
        updateControlsVisibility();

        if (dozing) {
            mOverlayContainer.setVisibility(INVISIBLE);
@@ -889,36 +895,28 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
    }

    private void setupControls() {
        if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
        boolean inNewLayout = mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
        boolean settingEnabled = Settings.Global.getInt(mContext.getContentResolver(),
                "controls_lockscreen", 0) == 1;
        if (!inNewLayout || !settingEnabled || !mControlsComponent.isEnabled()) {
            mAltLeftButton.setVisibility(View.GONE);
            mAltLeftButton.setOnClickListener(null);
            return;
        }

        if (Settings.Global.getInt(mContext.getContentResolver(), "controls_lockscreen", 0) == 0) {
            return;
        }

        if (mControlsComponent.getControlsListingController().isPresent()) {
        mControlsComponent.getControlsListingController().get()
                .addCallback(list -> {
                    if (!list.isEmpty()) {
                        mAltLeftButton.setImageDrawable(list.get(0).loadIcon());
                            mAltLeftButton.setVisibility(View.VISIBLE);
                        mAltLeftButton.setOnClickListener((v) -> {
                            ControlsUiController ui = mControlsComponent
                                    .getControlsUiController().get();
                            mControlsDialog = new ControlsDialog(mContext, mBroadcastDispatcher)
                                    .show(ui);
                        });

                        } else {
                            mAltLeftButton.setVisibility(View.GONE);
                            mAltLeftButton.setOnClickListener(null);
                    }
                    updateControlsVisibility();
                });
    }
    }

    /**
     * Optionally add controls when in the new lockscreen mode
+98 −13
Original line number Diff line number Diff line
@@ -17,11 +17,18 @@
package com.android.systemui.controls.dagger

import android.testing.AndroidTestingRunner
import android.provider.Settings
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.settings.SecureSettings
import dagger.Lazy
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -29,7 +36,11 @@ import org.junit.Assert.assertTrue
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.anyInt
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations

@SmallTest
@@ -42,20 +53,29 @@ class ControlsComponentTest : SysuiTestCase() {
    private lateinit var uiController: ControlsUiController
    @Mock
    private lateinit var listingController: ControlsListingController
    @Mock
    private lateinit var keyguardStateController: KeyguardStateController
    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    private lateinit var userTracker: UserTracker
    @Mock
    private lateinit var lockPatternUtils: LockPatternUtils
    @Mock
    private lateinit var secureSettings: SecureSettings

    companion object {
        fun <T> eq(value: T): T = Mockito.eq(value) ?: value
    }

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

        `when`(userTracker.userHandle.identifier).thenReturn(0)
    }

    @Test
    fun testFeatureEnabled() {
        val component = ControlsComponent(
                true,
                Lazy { controller },
                Lazy { uiController },
                Lazy { listingController }
        )
        val component = setupComponent(true)

        assertTrue(component.getControlsController().isPresent)
        assertEquals(controller, component.getControlsController().get())
@@ -67,15 +87,80 @@ class ControlsComponentTest : SysuiTestCase() {

    @Test
    fun testFeatureDisabled() {
        val component = ControlsComponent(
                false,
                Lazy { controller },
                Lazy { uiController },
                Lazy { listingController }
        )
        val component = setupComponent(false)

        assertFalse(component.getControlsController().isPresent)
        assertFalse(component.getControlsUiController().isPresent)
        assertFalse(component.getControlsListingController().isPresent)
    }

    @Test
    fun testFeatureDisabledVisibility() {
        val component = setupComponent(false)

        assertEquals(ControlsComponent.Visibility.UNAVAILABLE, component.getVisibility())
    }

    @Test
    fun testFeatureEnabledAfterBootVisibility() {
        `when`(controller.available).thenReturn(true)
        `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
            .thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT)
        val component = setupComponent(true)

        assertEquals(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK, component.getVisibility())
    }

    @Test
    fun testFeatureEnabledAndCannotShowOnLockScreenVisibility() {
        `when`(controller.available).thenReturn(true)
        `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
            .thenReturn(STRONG_AUTH_NOT_REQUIRED)
        `when`(keyguardStateController.isUnlocked()).thenReturn(false)
        `when`(secureSettings.getInt(eq(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), anyInt()))
            .thenReturn(0)
        val component = setupComponent(true)

        assertEquals(ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK, component.getVisibility())
    }

    @Test
    fun testFeatureEnabledAndCanShowOnLockScreenVisibility() {
        `when`(controller.available).thenReturn(true)
        `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
            .thenReturn(STRONG_AUTH_NOT_REQUIRED)
        `when`(keyguardStateController.isUnlocked()).thenReturn(false)
        `when`(secureSettings.getInt(eq(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), anyInt()))
            .thenReturn(1)
        val component = setupComponent(true)

        assertEquals(ControlsComponent.Visibility.AVAILABLE, component.getVisibility())
    }

    @Test
    fun testFeatureEnabledAndCanShowWhileUnlockedVisibility() {
        `when`(secureSettings.getInt(eq(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT), anyInt()))
            .thenReturn(0)
        `when`(controller.available).thenReturn(true)
        `when`(lockPatternUtils.getStrongAuthForUser(anyInt()))
            .thenReturn(STRONG_AUTH_NOT_REQUIRED)
        `when`(keyguardStateController.isUnlocked()).thenReturn(true)
        val component = setupComponent(true)

        assertEquals(ControlsComponent.Visibility.AVAILABLE, component.getVisibility())
    }

    private fun setupComponent(enabled: Boolean): ControlsComponent {
        return ControlsComponent(
            enabled,
            mContext,
            Lazy { controller },
            Lazy { uiController },
            Lazy { listingController },
            lockPatternUtils,
            keyguardStateController,
            userTracker,
            secureSettings
        )
    }
}
Loading