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

Commit 9d7132a3 authored by Nick Chameyev's avatar Nick Chameyev
Browse files

Update lock screen user switcher on settings update and re-create view stub...

Update lock screen user switcher on settings update and re-create view stub after disabling multi-user

NotificationPanelViewController inflates user switcher
view stub after enabling multi-user and removes user
switcher view after disabling this setting. After enabling
it again it crashed when trying to inflate view stub as
the view was removed. Fixed by re-inserting view stub
after disabling user switcher.

Another issue was that user switcher is not updated
immediately after changing the multi-user setting and after
switching displays. Fixed by adding settings observer and
listening for screen size changes in the controller.

Test: atest com.android.systemui.statusbar.phone.NotificationPanelViewTest
Fixes: 186728895
Change-Id: I1957abf952aff938406f4cabf4c83cd2977099e8
parent 04350c57
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ class ConfigurationControllerImpl(context: Context) : ConfigurationController {
    private val listeners: MutableList<ConfigurationController.ConfigurationListener> = ArrayList()
    private val lastConfig = Configuration()
    private var density: Int = 0
    private var smallestScreenWidth: Int = 0
    private var fontScale: Float = 0.toFloat()
    private val inCarMode: Boolean
    private var uiMode: Int = 0
@@ -38,6 +39,7 @@ class ConfigurationControllerImpl(context: Context) : ConfigurationController {
        this.context = context
        fontScale = currentConfig.fontScale
        density = currentConfig.densityDpi
        smallestScreenWidth = currentConfig.smallestScreenWidthDp
        inCarMode = currentConfig.uiMode and Configuration.UI_MODE_TYPE_MASK ==
                Configuration.UI_MODE_TYPE_CAR
        uiMode = currentConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
@@ -72,6 +74,14 @@ class ConfigurationControllerImpl(context: Context) : ConfigurationController {
            this.fontScale = fontScale
        }

        val smallestScreenWidth = newConfig.smallestScreenWidthDp
        if (smallestScreenWidth != this.smallestScreenWidth) {
            this.smallestScreenWidth = smallestScreenWidth
            listeners.filterForEach({ this.listeners.contains(it) }) {
                it.onSmallestScreenWidthChanged()
            }
        }

        val localeList = newConfig.locales
        if (localeList != this.localeList) {
            this.localeList = localeList
+64 −5
Original line number Diff line number Diff line
@@ -36,9 +36,11 @@ import android.animation.ValueAnimator;
import android.app.ActivityManager;
import android.app.Fragment;
import android.app.StatusBarManager;
import android.content.ContentResolver;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
@@ -50,10 +52,12 @@ import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.hardware.biometrics.BiometricSourceType;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.UserManager;
import android.os.VibrationEffect;
import android.provider.Settings;
import android.util.Log;
import android.util.MathUtils;
import android.view.LayoutInflater;
@@ -210,6 +214,8 @@ public class NotificationPanelViewController extends PanelViewController {
            new MyOnHeadsUpChangedListener();
    private final HeightListener mHeightListener = new HeightListener();
    private final ConfigurationListener mConfigurationListener = new ConfigurationListener();
    private final SettingsChangeObserver mSettingsChangeObserver;

    @VisibleForTesting final StatusBarStateListener mStatusBarStateListener =
            new StatusBarStateListener();
    private final BiometricUnlockController mBiometricUnlockController;
@@ -594,6 +600,8 @@ public class NotificationPanelViewController extends PanelViewController {
    private int mScreenCornerRadius;
    private boolean mQSAnimatingHiddenFromCollapsed;

    private final ContentResolver mContentResolver;

    private final Executor mUiExecutor;
    private final SecureSettings mSecureSettings;

@@ -635,6 +643,7 @@ public class NotificationPanelViewController extends PanelViewController {
    @Inject
    public NotificationPanelViewController(NotificationPanelView view,
            @Main Resources resources,
            @Main Handler handler,
            LayoutInflater layoutInflater,
            NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler,
            DynamicPrivacyController dynamicPrivacyController,
@@ -678,6 +687,7 @@ public class NotificationPanelViewController extends PanelViewController {
            TapAgainViewController tapAgainViewController,
            NavigationModeController navigationModeController,
            FragmentService fragmentService,
            ContentResolver contentResolver,
            QuickAccessWalletController quickAccessWalletController,
            @Main Executor uiExecutor,
            SecureSettings secureSettings,
@@ -704,15 +714,12 @@ public class NotificationPanelViewController extends PanelViewController {
        mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
        mDepthController = notificationShadeDepthController;
        mFeatureFlags = featureFlags;
        mContentResolver = contentResolver;
        mKeyguardQsUserSwitchComponentFactory = keyguardQsUserSwitchComponentFactory;
        mKeyguardUserSwitcherComponentFactory = keyguardUserSwitcherComponentFactory;
        mQSDetailDisplayer = qsDetailDisplayer;
        mFragmentService = fragmentService;
        mKeyguardUserSwitcherEnabled = mResources.getBoolean(
                com.android.internal.R.bool.config_keyguardUserSwitcher);
        mKeyguardQsUserSwitchEnabled =
                mKeyguardUserSwitcherEnabled && mResources.getBoolean(
                        R.bool.config_keyguard_user_switch_opens_qs_details);
        mSettingsChangeObserver = new SettingsChangeObserver(handler);
        mShouldUseSplitNotificationShade =
                Utils.shouldUseSplitNotificationShade(mFeatureFlags, mResources);
        mView.setWillNotDraw(!DEBUG);
@@ -795,6 +802,7 @@ public class NotificationPanelViewController extends PanelViewController {
        }

        mMaxKeyguardNotifications = resources.getInteger(R.integer.keyguard_max_notification_count);
        updateUserSwitcherFlags();
        onFinishInflate();
    }

@@ -1034,6 +1042,10 @@ public class NotificationPanelViewController extends PanelViewController {
                view = mLayoutInflater.inflate(layoutId, mView, false);
                mView.addView(view, index);
            } else {
                // Add the stub back so we can re-inflate it again if necessary
                ViewStub stub = new ViewStub(mView.getContext(), layoutId);
                stub.setId(stubId);
                mView.addView(stub, index);
                view = null;
            }
        } else if (enabled) {
@@ -1061,6 +1073,7 @@ public class NotificationPanelViewController extends PanelViewController {
        updateResources();

        // Re-inflate the keyguard user switcher group.
        updateUserSwitcherFlags();
        boolean isUserSwitcherEnabled = mUserManager.isUserSwitcherEnabled();
        boolean showQsUserSwitch = mKeyguardQsUserSwitchEnabled && isUserSwitcherEnabled;
        boolean showKeyguardUserSwitcher =
@@ -3895,6 +3908,26 @@ public class NotificationPanelViewController extends PanelViewController {
        return false;
    }

    private void updateUserSwitcherFlags() {
        mKeyguardUserSwitcherEnabled = mResources.getBoolean(
                com.android.internal.R.bool.config_keyguardUserSwitcher);
        mKeyguardQsUserSwitchEnabled =
                mKeyguardUserSwitcherEnabled && mResources.getBoolean(
                        R.bool.config_keyguard_user_switch_opens_qs_details);
    }

    private void registerSettingsChangeListener() {
        mContentResolver.registerContentObserver(
                Settings.Global.getUriFor(Settings.Global.USER_SWITCHER_ENABLED),
                /* notifyForDescendants */ false,
                mSettingsChangeObserver
        );
    }

    private void unregisterSettingsChangeListener() {
        mContentResolver.unregisterContentObserver(mSettingsChangeObserver);
    }

    private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
        @Override
        public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
@@ -4215,6 +4248,15 @@ public class NotificationPanelViewController extends PanelViewController {
            reInflateViews();
        }

        @Override
        public void onSmallestScreenWidthChanged() {
            if (DEBUG) Log.d(TAG, "onSmallestScreenWidthChanged");

            // Can affect multi-user switcher visibility as it depends on screen size by default:
            // it is enabled only for devices with large screens (see config_keyguardUserSwitcher)
            reInflateViews();
        }

        @Override
        public void onOverlayChanged() {
            if (DEBUG) Log.d(TAG, "onOverlayChanged");
@@ -4228,6 +4270,21 @@ public class NotificationPanelViewController extends PanelViewController {
        }
    }

    private class SettingsChangeObserver extends ContentObserver {

        SettingsChangeObserver(Handler handler) {
            super(handler);
        }

        @Override
        public void onChange(boolean selfChange) {
            if (DEBUG) Log.d(TAG, "onSettingsChanged");

            // Can affect multi-user switcher visibility
            reInflateViews();
        }
    }

    private class StatusBarStateListener implements StateListener {
        @Override
        public void onStateChanged(int statusBarState) {
@@ -4343,10 +4400,12 @@ public class NotificationPanelViewController extends PanelViewController {
            mConfigurationListener.onThemeChanged();
            mFalsingManager.addTapListener(mFalsingTapListener);
            mKeyguardIndicationController.init();
            registerSettingsChangeListener();
        }

        @Override
        public void onViewDetachedFromWindow(View v) {
            unregisterSettingsChangeListener();
            mFragmentService.getFragmentHostManager(mView)
                            .removeTagListener(QS.TAG, mFragmentListener);
            mStatusBarStateController.removeCallback(mStatusBarStateListener);
+1 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ public interface ConfigurationController extends CallbackController<Configuratio
    interface ConfigurationListener {
        default void onConfigChanged(Configuration newConfig) {}
        default void onDensityOrFontScaleChanged() {}
        default void onSmallestScreenWidthChanged() {}
        default void onOverlayChanged() {}
        default void onUiModeChanged() {}
        default void onThemeChanged() {}
+84 −2
Original line number Diff line number Diff line
@@ -26,9 +26,12 @@ import static com.android.systemui.statusbar.notification.ViewGroupFadeHelper.re
import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -37,9 +40,13 @@ import static org.mockito.Mockito.when;

import android.annotation.IdRes;
import android.app.ActivityManager;
import android.content.ContentResolver;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.biometrics.BiometricSourceType;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.os.UserManager;
import android.testing.AndroidTestingRunner;
@@ -50,6 +57,7 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.ViewStub;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;

@@ -60,6 +68,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardClockSwitch;
import com.android.keyguard.KeyguardClockSwitchController;
@@ -140,6 +149,7 @@ public class NotificationPanelViewTest extends SysuiTestCase {
    private KeyguardBottomAreaView mKeyguardBottomArea;
    @Mock
    private KeyguardBottomAreaView mQsFrame;
    private KeyguardStatusView mKeyguardStatusView;
    @Mock
    private ViewGroup mBigClockContainer;
    @Mock
@@ -153,6 +163,10 @@ public class NotificationPanelViewTest extends SysuiTestCase {
    @Mock
    private KeyguardStatusBarView mKeyguardStatusBar;
    @Mock
    private View mUserSwitcherView;
    @Mock
    private ViewStub mUserSwitcherStubView;
    @Mock
    private HeadsUpTouchHelper.Callback mHeadsUpCallback;
    @Mock
    private PanelBar mPanelBar;
@@ -204,7 +218,6 @@ public class NotificationPanelViewTest extends SysuiTestCase {
    @Mock
    private KeyguardClockSwitch mKeyguardClockSwitch;
    private PanelViewController.TouchHandler mTouchHandler;
    @Mock
    private ConfigurationController mConfigurationController;
    @Mock
    private MediaHierarchyManager mMediaHiearchyManager;
@@ -265,6 +278,8 @@ public class NotificationPanelViewTest extends SysuiTestCase {
    @Mock
    private SecureSettings mSecureSettings;
    @Mock
    private ContentResolver mContentResolver;
    @Mock
    private TapAgainViewController mTapAgainViewController;
    @Mock
    private KeyguardIndicationController mKeyguardIndicationController;
@@ -287,6 +302,9 @@ public class NotificationPanelViewTest extends SysuiTestCase {
        MockitoAnnotations.initMocks(this);
        mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger);

        mKeyguardStatusView = new KeyguardStatusView(mContext);
        mKeyguardStatusView.setId(R.id.keyguard_status_view);

        when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
        when(mHeadsUpCallback.getContext()).thenReturn(mContext);
        when(mView.getResources()).thenReturn(mResources);
@@ -301,6 +319,9 @@ public class NotificationPanelViewTest extends SysuiTestCase {
        when(mResources.getDimensionPixelSize(R.dimen.notification_panel_width)).thenReturn(400);
        when(mView.getContext()).thenReturn(getContext());
        when(mView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar);
        when(mView.findViewById(R.id.keyguard_user_switcher_view)).thenReturn(mUserSwitcherView);
        when(mView.findViewById(R.id.keyguard_user_switcher_stub)).thenReturn(
                mUserSwitcherStubView);
        when(mView.findViewById(R.id.keyguard_clock_container)).thenReturn(mKeyguardClockSwitch);
        when(mView.findViewById(R.id.notification_stack_scroller))
                .thenReturn(mNotificationStackScrollLayout);
@@ -320,7 +341,7 @@ public class NotificationPanelViewTest extends SysuiTestCase {
        mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null);
        mNotificationContainerParent.addView(newViewWithId(R.id.qs_frame));
        mNotificationContainerParent.addView(newViewWithId(R.id.notification_stack_scroller));
        mNotificationContainerParent.addView(newViewWithId(R.id.keyguard_status_view));
        mNotificationContainerParent.addView(mKeyguardStatusView);
        when(mView.findViewById(R.id.notification_container_parent))
                .thenReturn(mNotificationContainerParent);
        when(mFragmentService.getFragmentHostManager(mView)).thenReturn(mFragmentHostManager);
@@ -348,6 +369,7 @@ public class NotificationPanelViewTest extends SysuiTestCase {
                mFalsingManager,
                mLockscreenShadeTransitionController,
                new FalsingCollectorFake());
        mConfigurationController = new ConfigurationControllerImpl(mContext);
        when(mKeyguardStatusViewComponentFactory.build(any()))
                .thenReturn(mKeyguardStatusViewComponent);
        when(mKeyguardStatusViewComponent.getKeyguardClockSwitchController())
@@ -358,10 +380,16 @@ public class NotificationPanelViewTest extends SysuiTestCase {
                .thenReturn(mKeyguardStatusBarViewComponent);
        when(mKeyguardStatusBarViewComponent.getKeyguardStatusBarViewController())
                .thenReturn(mKeyguardStatusBarViewController);
        when(mLayoutInflater.inflate(eq(R.layout.keyguard_status_view), any(), anyBoolean()))
                .thenReturn(mKeyguardStatusView);
        when(mLayoutInflater.inflate(eq(R.layout.keyguard_bottom_area), any(), anyBoolean()))
                .thenReturn(mKeyguardBottomArea);

        reset(mView);

        mNotificationPanelViewController = new NotificationPanelViewController(mView,
                mResources,
                new Handler(Looper.getMainLooper()),
                mLayoutInflater,
                coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController,
                mFalsingManager, new FalsingCollectorFake(),
@@ -395,6 +423,7 @@ public class NotificationPanelViewTest extends SysuiTestCase {
                mTapAgainViewController,
                mNavigationModeController,
                mFragmentService,
                mContentResolver,
                mQuickAccessWalletController,
                new FakeExecutor(new FakeSystemClock()),
                mSecureSettings,
@@ -548,6 +577,38 @@ public class NotificationPanelViewTest extends SysuiTestCase {
                .isEqualTo(R.id.qs_edge_guideline);
    }

    @Test
    public void testDisableUserSwitcherAfterEnabling_returnsViewStubToTheViewHierarchy() {
        givenViewAttached();
        when(mResources.getBoolean(
                com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true);
        updateMultiUserSetting(true);
        clearInvocations(mView);

        updateMultiUserSetting(false);

        ArgumentCaptor<View> captor = ArgumentCaptor.forClass(View.class);
        verify(mView, atLeastOnce()).addView(captor.capture(), anyInt());
        final View userSwitcherStub = CollectionUtils.find(captor.getAllValues(),
                view -> view.getId() == R.id.keyguard_user_switcher_stub);
        assertThat(userSwitcherStub).isNotNull();
        assertThat(userSwitcherStub).isInstanceOf(ViewStub.class);
    }

    @Test
    public void testChangeSmallestScreenWidthAndUserSwitchEnabled_inflatesUserSwitchView() {
        givenViewAttached();
        when(mView.findViewById(R.id.keyguard_user_switcher_view)).thenReturn(null);
        updateSmallestScreenWidth(300);
        when(mResources.getBoolean(
                com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true);
        when(mUserManager.isUserSwitcherEnabled()).thenReturn(true);

        updateSmallestScreenWidth(800);

        verify(mUserSwitcherStubView).inflate();
    }

    @Test
    public void testSplitShadeLayout_isAlignedToGuideline() {
        enableSplitShade();
@@ -682,6 +743,12 @@ public class NotificationPanelViewTest extends SysuiTestCase {
        return mFalsingManager.getTapListeners().get(0);
    }

    private void givenViewAttached() {
        for (View.OnAttachStateChangeListener listener : mOnAttachStateChangeListeners) {
            listener.onViewAttachedToWindow(mView);
        }
    }

    private View newViewWithId(int id) {
        View view = new View(mContext);
        view.setId(id);
@@ -704,6 +771,21 @@ public class NotificationPanelViewTest extends SysuiTestCase {
        mNotificationPanelViewController.updateResources();
    }

    private void updateMultiUserSetting(boolean enabled) {
        when(mUserManager.isUserSwitcherEnabled()).thenReturn(enabled);
        final ArgumentCaptor<ContentObserver> observerCaptor =
                ArgumentCaptor.forClass(ContentObserver.class);
        verify(mContentResolver)
                .registerContentObserver(any(), anyBoolean(), observerCaptor.capture());
        observerCaptor.getValue().onChange(/* selfChange */ false);
    }

    private void updateSmallestScreenWidth(int smallestScreenWidthDp) {
        Configuration configuration = new Configuration();
        configuration.smallestScreenWidthDp = smallestScreenWidthDp;
        mConfigurationController.onConfigurationChanged(configuration);
    }

    private void onTouchEvent(MotionEvent ev) {
        mTouchHandler.onTouch(mView, ev);
    }