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

Commit 5dc67de1 authored by kwaky's avatar kwaky
Browse files

Add occlusion logic to OverlayViewGlobalStateController and CarKeyguardViewController

Activity has a public API setShowWhenLocked, which, if set to true,
allows it to be shown when the device is locked, thereby "occluding"
the Keyguard.

In the Phone OS implementation, when an occluding Activity is showing,
Keyguard is temporarily dismissed and StatusBar expands.

In the AAOS implementation where SystemUI components are mounted to
the SystemUIOverlayWindow, OverlayViewGlobalStateController hides all
views mounted to it unless they are configured to be shown even during
occlusion.

Test: Unit Tests + atest CtsWindowManagerDeviceTestCases:ActivityLifecycleTopResumedStateTests#testTopPositionLaunchedOnTopOfLockScreen
Bug: 156781505
Change-Id: I8320e97a575a990ba4301729c6b0e6c86d6ef7c5
parent 82352f9d
Loading
Loading
Loading
Loading
+8 −5
Original line number Diff line number Diff line
@@ -218,6 +218,14 @@ public class CarKeyguardViewController extends OverlayViewController implements
        }
    }

    @Override
    public void setOccluded(boolean occluded, boolean animate) {
        getOverlayViewGlobalStateController().setOccluded(occluded);
        if (!occluded) {
            reset(/* hideBouncerWhenShowing= */ false);
        }
    }

    @Override
    public void onCancelClicked() {
        if (mBouncer == null) return;
@@ -314,11 +322,6 @@ public class CarKeyguardViewController extends OverlayViewController implements
        // no-op
    }

    @Override
    public void setOccluded(boolean occluded, boolean animate) {
        // no-op
    }

    @Override
    public boolean shouldDisableWindowAnimationsForUnlock() {
        return false;
+7 −0
Original line number Diff line number Diff line
@@ -138,4 +138,11 @@ public class OverlayViewController {
    protected boolean shouldShowNavigationBar() {
        return false;
    }

    /**
     * Returns {@code true} if this view should be hidden during the occluded state.
     */
    protected boolean shouldShowWhenOccluded() {
        return false;
    }
}
+56 −0
Original line number Diff line number Diff line
@@ -24,7 +24,9 @@ import androidx.annotation.VisibleForTesting;
import com.android.systemui.car.navigationbar.CarNavigationBarController;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

@@ -47,11 +49,16 @@ public class OverlayViewGlobalStateController {
    private static final int UNKNOWN_Z_ORDER = -1;
    private final SystemUIOverlayWindowController mSystemUIOverlayWindowController;
    private final CarNavigationBarController mCarNavigationBarController;

    private boolean mIsOccluded;

    @VisibleForTesting
    Map<OverlayViewController, Integer> mZOrderMap;
    @VisibleForTesting
    SortedMap<Integer, OverlayViewController> mZOrderVisibleSortedMap;
    @VisibleForTesting
    Set<OverlayViewController> mViewsHiddenForOcclusion;
    @VisibleForTesting
    OverlayViewController mHighestZOrder;

    @Inject
@@ -63,6 +70,7 @@ public class OverlayViewGlobalStateController {
        mCarNavigationBarController = carNavigationBarController;
        mZOrderMap = new HashMap<>();
        mZOrderVisibleSortedMap = new TreeMap<>();
        mViewsHiddenForOcclusion = new HashSet<>();
    }

    /**
@@ -91,6 +99,10 @@ public class OverlayViewGlobalStateController {
     */
    public void showView(OverlayViewController viewController, @Nullable Runnable show) {
        debugLog();
        if (mIsOccluded && !viewController.shouldShowWhenOccluded()) {
            mViewsHiddenForOcclusion.add(viewController);
            return;
        }
        if (mZOrderVisibleSortedMap.isEmpty()) {
            setWindowVisible(true);
        }
@@ -147,6 +159,10 @@ public class OverlayViewGlobalStateController {
     */
    public void hideView(OverlayViewController viewController, @Nullable Runnable hide) {
        debugLog();
        if (mIsOccluded && mViewsHiddenForOcclusion.contains(viewController)) {
            mViewsHiddenForOcclusion.remove(viewController);
            return;
        }
        if (!viewController.isInflated()) {
            Log.d(TAG, "Content cannot be hidden since it isn't inflated: "
                    + viewController.getClass().getName());
@@ -240,6 +256,43 @@ public class OverlayViewGlobalStateController {
        return mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowHUN();
    }

    /**
     * Set the OverlayViewWindow to be in occluded or unoccluded state. When OverlayViewWindow is
     * occluded, all views mounted to it that are not configured to be shown during occlusion will
     * be hidden.
     */
    public void setOccluded(boolean occluded) {
        if (occluded) {
            // Hide views before setting mIsOccluded to true so the regular hideView logic is used,
            // not the one used during occlusion.
            hideViewsForOcclusion();
            mIsOccluded = true;
        } else {
            mIsOccluded = false;
            // show views after setting mIsOccluded to false so the regular showView logic is used,
            // not the one used during occlusion.
            showViewsHiddenForOcclusion();
        }
    }

    private void hideViewsForOcclusion() {
        HashSet<OverlayViewController> viewsCurrentlyShowing = new HashSet<>(
                mZOrderVisibleSortedMap.values());
        viewsCurrentlyShowing.forEach(overlayController -> {
            if (!overlayController.shouldShowWhenOccluded()) {
                hideView(overlayController, overlayController::hideInternal);
                mViewsHiddenForOcclusion.add(overlayController);
            }
        });
    }

    private void showViewsHiddenForOcclusion() {
        mViewsHiddenForOcclusion.forEach(overlayViewController -> {
            showView(overlayViewController, overlayViewController::showInternal);
        });
        mViewsHiddenForOcclusion.clear();
    }

    private void debugLog() {
        if (!DEBUG) {
            return;
@@ -250,5 +303,8 @@ public class OverlayViewGlobalStateController {
        Log.d(TAG, "mZOrderVisibleSortedMap: " + mZOrderVisibleSortedMap);
        Log.d(TAG, "mZOrderMap.size(): " + mZOrderMap.size());
        Log.d(TAG, "mZOrderMap: " + mZOrderMap);
        Log.d(TAG, "mIsOccluded: " + mIsOccluded);
        Log.d(TAG, "mViewsHiddenForOcclusion: " + mViewsHiddenForOcclusion);
        Log.d(TAG, "mViewsHiddenForOcclusion.size(): " + mViewsHiddenForOcclusion.size());
    }
}
+13 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@@ -168,6 +169,18 @@ public class CarKeyguardViewControllerTest extends SysuiTestCase {
                any());
    }

    @Test
    public void setOccludedFalse_currentlyOccluded_bouncerReset() {
        when(mBouncer.isSecure()).thenReturn(true);
        mCarKeyguardViewController.show(/* options= */ null);
        mCarKeyguardViewController.setOccluded(/* occluded= */ true, /* animate= */ false);
        reset(mBouncer);

        mCarKeyguardViewController.setOccluded(/* occluded= */ false, /* animate= */ false);

        verify(mBouncer).show(/* resetSecuritySelection= */ true);
    }

    @Test
    public void onCancelClicked_callsCancelClickedListener() {
        when(mBouncer.isSecure()).thenReturn(true);
+75 −0
Original line number Diff line number Diff line
@@ -490,6 +490,81 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase {
        verify(mSystemUIOverlayWindowController).setWindowVisible(false);
    }

    @Test
    public void setOccludedTrue_viewToHideWhenOccludedVisible_viewHidden() {
        setupOverlayViewController1();
        setOverlayViewControllerAsShowing(mOverlayViewController1);
        when(mOverlayViewController1.shouldShowWhenOccluded()).thenReturn(false);

        mOverlayViewGlobalStateController.setOccluded(true);

        assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsValue(
                mOverlayViewController1)).isFalse();
    }

    @Test
    public void setOccludedTrue_viewToNotHideWhenOccludedVisible_viewShown() {
        setupOverlayViewController1();
        setOverlayViewControllerAsShowing(mOverlayViewController1);
        when(mOverlayViewController1.shouldShowWhenOccluded()).thenReturn(true);

        mOverlayViewGlobalStateController.setOccluded(true);

        assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsValue(
                mOverlayViewController1)).isTrue();
    }

    @Test
    public void hideViewAndThenSetOccludedTrue_viewHiddenForOcclusion_viewHiddenAfterOcclusion() {
        setupOverlayViewController1();
        setOverlayViewControllerAsShowing(mOverlayViewController1);
        when(mOverlayViewController1.shouldShowWhenOccluded()).thenReturn(false);
        mOverlayViewGlobalStateController.setOccluded(true);

        mOverlayViewGlobalStateController.hideView(mOverlayViewController1, /* runnable= */ null);
        mOverlayViewGlobalStateController.setOccluded(false);

        assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsValue(
                mOverlayViewController1)).isFalse();
    }

    @Test
    public void setOccludedTrueAndThenShowView_viewToNotHideForOcclusion_viewShown() {
        setupOverlayViewController1();
        when(mOverlayViewController1.shouldShowWhenOccluded()).thenReturn(true);

        mOverlayViewGlobalStateController.setOccluded(true);
        setOverlayViewControllerAsShowing(mOverlayViewController1);

        assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsValue(
                mOverlayViewController1)).isTrue();
    }

    @Test
    public void setOccludedTrueAndThenShowView_viewToHideForOcclusion_viewHidden() {
        setupOverlayViewController1();
        when(mOverlayViewController1.shouldShowWhenOccluded()).thenReturn(false);

        mOverlayViewGlobalStateController.setOccluded(true);
        setOverlayViewControllerAsShowing(mOverlayViewController1);

        assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsValue(
                mOverlayViewController1)).isFalse();
    }

    @Test
    public void setOccludedFalse_viewShownAfterSetOccludedTrue_viewToHideForOcclusion_viewShown() {
        setupOverlayViewController1();
        when(mOverlayViewController1.shouldShowWhenOccluded()).thenReturn(false);
        mOverlayViewGlobalStateController.setOccluded(true);
        setOverlayViewControllerAsShowing(mOverlayViewController1);

        mOverlayViewGlobalStateController.setOccluded(false);

        assertThat(mOverlayViewGlobalStateController.mZOrderVisibleSortedMap.containsValue(
                mOverlayViewController1)).isTrue();
    }

    @Test
    public void inflateView_notInflated_inflates() {
        when(mOverlayViewController2.isInflated()).thenReturn(false);