Remove QS background
QS background was removed and elements around it were adjusted to match the new Silk style. This includes showing a scrim when pulling it down from the lock screen, fading away the clock, and changing the color of some UI elements. Fixes: 173562510 Test: atest ScrimControllerTest Test: atest KeyguardClockPositionAlgorithmTest Test: manual Change-Id: Ie995121d85db9034860f4a70182d6328b4b6f0b3
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2017 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.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:alpha="1"
|
||||
android:color="?android:attr/colorBackground"/>
|
||||
</selector>
|
||||
@@ -15,6 +15,6 @@
|
||||
~ limitations under the License
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/qs_background_dark" />
|
||||
<solid android:color="?android:attr/colorBackground" />
|
||||
<corners android:radius="8dp" />
|
||||
</shape>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 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.
|
||||
-->
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<shape>
|
||||
<solid android:color="@color/qs_background_dark"/>
|
||||
<corners android:radius="?android:attr/dialogCornerRadius" />
|
||||
</shape>
|
||||
</inset>
|
||||
@@ -17,6 +17,6 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle" >
|
||||
<solid
|
||||
android:color="#e5e5e5" />
|
||||
android:color="?android:attr/textColorPrimary" />
|
||||
<corners android:radius="2dp" />
|
||||
</shape>
|
||||
|
||||
@@ -25,9 +25,7 @@
|
||||
<View
|
||||
android:id="@+id/quick_settings_background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:elevation="4dp"
|
||||
android:background="@drawable/qs_background_primary" />
|
||||
android:layout_height="0dp" />
|
||||
|
||||
<com.android.systemui.qs.NonInterceptingScrollView
|
||||
android:id="@+id/expanded_qs_scroll_view"
|
||||
|
||||
@@ -25,5 +25,4 @@
|
||||
android:layout_marginTop="@dimen/notification_side_paddings"
|
||||
android:layout_marginLeft="@dimen/notification_side_paddings"
|
||||
android:layout_marginRight="@dimen/notification_side_paddings"
|
||||
android:visibility="gone"
|
||||
android:background="@drawable/qs_background_primary"/>
|
||||
android:visibility="gone"/>
|
||||
|
||||
@@ -103,7 +103,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks
|
||||
|
||||
d.setOnShowListener(dialog -> {
|
||||
if (mBlurUtils.supportsBlursOnWindows()) {
|
||||
background.setAlpha((int) (ScrimController.BLUR_SCRIM_ALPHA * 255));
|
||||
background.setAlpha((int) (ScrimController.BUSY_SCRIM_ALPHA * 255));
|
||||
mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(),
|
||||
mBlurUtils.blurRadiusOfRatio(1));
|
||||
} else {
|
||||
|
||||
@@ -116,6 +116,11 @@ public class KeyguardClockPositionAlgorithm {
|
||||
*/
|
||||
private float mDarkAmount;
|
||||
|
||||
/**
|
||||
* How visible the quick settings panel is.
|
||||
*/
|
||||
private float mQsExpansion;
|
||||
|
||||
private float mEmptyDragAmount;
|
||||
|
||||
/**
|
||||
@@ -159,7 +164,8 @@ public class KeyguardClockPositionAlgorithm {
|
||||
public void setup(int statusBarMinHeight, int maxShadeBottom, int notificationStackHeight,
|
||||
float panelExpansion, int parentHeight, int keyguardStatusHeight, int clockPreferredY,
|
||||
boolean hasCustomClock, boolean hasVisibleNotifs, float dark, float emptyDragAmount,
|
||||
boolean bypassEnabled, int unlockedStackScrollerPadding, boolean udfpsEnrolled) {
|
||||
boolean bypassEnabled, int unlockedStackScrollerPadding, boolean udfpsEnrolled,
|
||||
float qsExpansion) {
|
||||
mMinTopMargin = statusBarMinHeight + (udfpsEnrolled ? mContainerTopPaddingWithoutLockIcon :
|
||||
mContainerTopPaddingWithLockIcon);
|
||||
mMaxShadeBottom = maxShadeBottom;
|
||||
@@ -174,6 +180,7 @@ public class KeyguardClockPositionAlgorithm {
|
||||
mEmptyDragAmount = emptyDragAmount;
|
||||
mBypassEnabled = bypassEnabled;
|
||||
mUnlockedStackScrollerPadding = unlockedStackScrollerPadding;
|
||||
mQsExpansion = qsExpansion;
|
||||
}
|
||||
|
||||
public void run(Result result) {
|
||||
@@ -274,6 +281,7 @@ public class KeyguardClockPositionAlgorithm {
|
||||
*/
|
||||
private float getClockAlpha(int y) {
|
||||
float alphaKeyguard = Math.max(0, y / Math.max(1f, getClockY(1f)));
|
||||
alphaKeyguard *= (1f - mQsExpansion);
|
||||
alphaKeyguard = Interpolators.ACCELERATE.getInterpolation(alphaKeyguard);
|
||||
return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount);
|
||||
}
|
||||
|
||||
@@ -292,6 +292,7 @@ public class NotificationPanelViewController extends PanelViewController {
|
||||
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
|
||||
private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
|
||||
private final QSDetailDisplayer mQSDetailDisplayer;
|
||||
private final ScrimController mScrimController;
|
||||
// Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow card.
|
||||
// If there are exactly 1 + mMaxKeyguardNotifications, then still shows all notifications
|
||||
private final int mMaxKeyguardNotifications;
|
||||
@@ -549,6 +550,7 @@ public class NotificationPanelViewController extends PanelViewController {
|
||||
NotificationIconAreaController notificationIconAreaController,
|
||||
AuthController authController,
|
||||
QSDetailDisplayer qsDetailDisplayer,
|
||||
ScrimController scrimController,
|
||||
MediaDataManager mediaDataManager) {
|
||||
super(view, falsingManager, dozeLog, keyguardStateController,
|
||||
(SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
|
||||
@@ -579,6 +581,7 @@ public class NotificationPanelViewController extends PanelViewController {
|
||||
mPulseExpansionHandler = pulseExpansionHandler;
|
||||
mDozeParameters = dozeParameters;
|
||||
mBiometricUnlockController = biometricUnlockController;
|
||||
mScrimController = scrimController;
|
||||
mMediaDataManager = mediaDataManager;
|
||||
pulseExpansionHandler.setPulseExpandAbortListener(() -> {
|
||||
if (mQs != null) {
|
||||
@@ -909,7 +912,7 @@ public class NotificationPanelViewController extends PanelViewController {
|
||||
clockPreferredY, hasCustomClock(),
|
||||
hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
|
||||
bypassEnabled, getUnlockedStackScrollerPadding(),
|
||||
mUpdateMonitor.isUdfpsEnrolled());
|
||||
mUpdateMonitor.isUdfpsEnrolled(), getQsExpansionFraction());
|
||||
mClockPositionAlgorithm.run(mClockPositionResult);
|
||||
mKeyguardStatusViewController.updatePosition(
|
||||
mClockPositionResult.clockX, mClockPositionResult.clockY,
|
||||
@@ -1756,6 +1759,7 @@ public class NotificationPanelViewController extends PanelViewController {
|
||||
updateHeaderKeyguardAlpha();
|
||||
if (mBarState == StatusBarState.SHADE_LOCKED || mBarState == KEYGUARD) {
|
||||
updateKeyguardBottomAreaAlpha();
|
||||
positionClockAndNotifications();
|
||||
updateBigClockAlpha();
|
||||
}
|
||||
|
||||
@@ -1782,6 +1786,7 @@ public class NotificationPanelViewController extends PanelViewController {
|
||||
float qsExpansionFraction = getQsExpansionFraction();
|
||||
mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
|
||||
mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
|
||||
mScrimController.setQsExpansion(qsExpansionFraction);
|
||||
mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction);
|
||||
}
|
||||
|
||||
|
||||
@@ -125,11 +125,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
|
||||
*/
|
||||
public static final float BUSY_SCRIM_ALPHA = 1f;
|
||||
|
||||
/**
|
||||
* Same as above, but when blur is supported.
|
||||
*/
|
||||
public static final float BLUR_SCRIM_ALPHA = 0.80f;
|
||||
|
||||
static final int TAG_KEY_ANIM = R.id.scrim;
|
||||
private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
|
||||
private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
|
||||
@@ -161,6 +156,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
|
||||
|
||||
// Assuming the shade is expanded during initialization
|
||||
private float mExpansionFraction = 1f;
|
||||
private float mQsExpansion;
|
||||
|
||||
private boolean mDarkenWhileDragging;
|
||||
private boolean mExpansionAffectsAlpha = true;
|
||||
@@ -206,8 +202,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
|
||||
BlurUtils blurUtils, ConfigurationController configurationController) {
|
||||
|
||||
mScrimStateListener = lightBarController::setScrimState;
|
||||
mDefaultScrimAlpha = blurUtils.supportsBlursOnWindows()
|
||||
? BLUR_SCRIM_ALPHA : BUSY_SCRIM_ALPHA;
|
||||
mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
|
||||
mBlurUtils = blurUtils;
|
||||
|
||||
mKeyguardStateController = keyguardStateController;
|
||||
@@ -463,6 +458,31 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
|
||||
mExpansionFraction = fraction;
|
||||
|
||||
boolean relevantState = (mState == ScrimState.UNLOCKED
|
||||
|| mState == ScrimState.KEYGUARD
|
||||
|| mState == ScrimState.SHADE_LOCKED
|
||||
|| mState == ScrimState.PULSING
|
||||
|| mState == ScrimState.BUBBLE_EXPANDED);
|
||||
if (!(relevantState && mExpansionAffectsAlpha)) {
|
||||
return;
|
||||
}
|
||||
applyAndDispatchExpansion();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Current state of the QuickSettings expansion when pulling it from the top.
|
||||
*
|
||||
* @param fraction From 0 to 1 where 0 means collapsed and 1 expanded.
|
||||
*/
|
||||
public void setQsExpansion(float fraction) {
|
||||
if (isNaN(fraction)) {
|
||||
return;
|
||||
}
|
||||
if (mQsExpansion != fraction) {
|
||||
mQsExpansion = fraction;
|
||||
Log.d(TAG, "set qs fraction");
|
||||
|
||||
boolean relevantState = (mState == ScrimState.SHADE_LOCKED
|
||||
|| mState == ScrimState.KEYGUARD
|
||||
|| mState == ScrimState.PULSING
|
||||
|| mState == ScrimState.BUBBLE_EXPANDED);
|
||||
@@ -506,7 +526,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
|
||||
behindFraction = (float) Math.pow(behindFraction, 0.8f);
|
||||
mBehindAlpha = behindFraction * mDefaultScrimAlpha;
|
||||
mInFrontAlpha = 0;
|
||||
} else if (mState == ScrimState.KEYGUARD || mState == ScrimState.PULSING) {
|
||||
} else if (mState == ScrimState.KEYGUARD || mState == ScrimState.SHADE_LOCKED
|
||||
|| mState == ScrimState.PULSING) {
|
||||
// Either darken of make the scrim transparent when you
|
||||
// pull down the shade
|
||||
float interpolatedFract = getInterpolatedFraction();
|
||||
@@ -522,6 +543,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
|
||||
}
|
||||
mBehindTint = ColorUtils.blendARGB(ScrimState.BOUNCER.getBehindTint(),
|
||||
mState.getBehindTint(), interpolatedFract);
|
||||
if (mQsExpansion > 0) {
|
||||
mBehindAlpha = MathUtils.lerp(mBehindAlpha, mDefaultScrimAlpha, mQsExpansion);
|
||||
mBehindTint = ColorUtils.blendARGB(mBehindTint,
|
||||
ScrimState.SHADE_LOCKED.getBehindTint(), mQsExpansion);
|
||||
}
|
||||
}
|
||||
if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha)) {
|
||||
throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
|
||||
|
||||
@@ -112,6 +112,15 @@ public enum ScrimState {
|
||||
}
|
||||
},
|
||||
|
||||
SHADE_LOCKED {
|
||||
@Override
|
||||
public void prepare(ScrimState previousState) {
|
||||
mBehindAlpha = mDefaultScrimAlpha;
|
||||
mBubbleAlpha = 0f;
|
||||
mFrontAlpha = 0f;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Changing screen brightness from quick settings.
|
||||
*/
|
||||
|
||||
@@ -4091,6 +4091,8 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
|
||||
} else if (mBrightnessMirrorVisible) {
|
||||
mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
|
||||
} else if (mState == StatusBarState.SHADE_LOCKED) {
|
||||
mScrimController.transitionTo(ScrimState.SHADE_LOCKED);
|
||||
} else if (mDozeServiceHost.isPulsing()) {
|
||||
mScrimController.transitionTo(ScrimState.PULSING,
|
||||
mDozeScrimController.getScrimCallback());
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar.phone;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
@@ -31,7 +30,6 @@ import org.junit.runner.RunWith;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
|
||||
|
||||
private static final int SCREEN_HEIGHT = 2000;
|
||||
@@ -53,6 +51,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
|
||||
private int mPreferredClockY;
|
||||
private boolean mHasCustomClock;
|
||||
private boolean mHasVisibleNotifs;
|
||||
private float mQsExpansion;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@@ -354,6 +353,17 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
|
||||
assertThat(mClockPosition.clockAlpha).isEqualTo(OPAQUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clockHiddenWhenQsIsExpanded() {
|
||||
// GIVEN on the lock screen with a custom clock and visible notifications
|
||||
givenLockScreen();
|
||||
mQsExpansion = 1;
|
||||
// WHEN the clock position algorithm is run
|
||||
positionClock();
|
||||
// THEN the clock Y position is the middle of the screen (SCREEN_HEIGHT / 2).
|
||||
assertThat(mClockPosition.clockAlpha).isEqualTo(TRANSPARENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preferredCustomClockPositionWithVisibleNotificationsOnAod() {
|
||||
// GIVEN on the lock screen with a custom clock and visible notifications
|
||||
@@ -384,7 +394,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
|
||||
mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight,
|
||||
mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight, mPreferredClockY,
|
||||
mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
|
||||
0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */);
|
||||
0 /* unlockedStackScrollerPadding */, false /* udfpsEnrolled */, mQsExpansion);
|
||||
mClockPositionAlgorithm.run(mClockPosition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +199,8 @@ public class NotificationPanelViewTest extends SysuiTestCase {
|
||||
@Mock
|
||||
private AuthController mAuthController;
|
||||
@Mock
|
||||
private ScrimController mScrimController;
|
||||
@Mock
|
||||
private MediaDataManager mMediaDataManager;
|
||||
|
||||
private NotificationPanelViewController mNotificationPanelViewController;
|
||||
@@ -279,6 +281,7 @@ public class NotificationPanelViewTest extends SysuiTestCase {
|
||||
mNotificationAreaController,
|
||||
mAuthController,
|
||||
new QSDetailDisplayer(),
|
||||
mScrimController,
|
||||
mMediaDataManager);
|
||||
mNotificationPanelViewController.initDependencies(
|
||||
mStatusBar,
|
||||
|
||||
@@ -248,6 +248,20 @@ public class ScrimControllerTest extends SysuiTestCase {
|
||||
false /* bubble */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transitionToShadeLocked() {
|
||||
mScrimController.transitionTo(ScrimState.SHADE_LOCKED);
|
||||
finishAnimationsImmediately();
|
||||
|
||||
assertScrimAlpha(TRANSPARENT /* front */,
|
||||
OPAQUE /* back */,
|
||||
TRANSPARENT /* bubble */);
|
||||
|
||||
assertScrimTint(false /* front */,
|
||||
false /* behind */,
|
||||
false /* bubble */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transitionToOff() {
|
||||
mScrimController.transitionTo(ScrimState.OFF);
|
||||
@@ -464,7 +478,7 @@ public class ScrimControllerTest extends SysuiTestCase {
|
||||
// Front scrim should be transparent
|
||||
// Back scrim should be visible without tint
|
||||
assertScrimAlpha(TRANSPARENT /* front */,
|
||||
SEMI_TRANSPARENT /* back */,
|
||||
OPAQUE /* back */,
|
||||
TRANSPARENT /* bubble */);
|
||||
|
||||
assertScrimTint(false /* front */,
|
||||
@@ -478,7 +492,7 @@ public class ScrimControllerTest extends SysuiTestCase {
|
||||
finishAnimationsImmediately();
|
||||
// Front scrim should be transparent
|
||||
// Back scrim should be visible without tint
|
||||
assertScrimAlpha(SEMI_TRANSPARENT /* front */,
|
||||
assertScrimAlpha(OPAQUE /* front */,
|
||||
TRANSPARENT /* back */,
|
||||
TRANSPARENT /* bubble */);
|
||||
assertScrimTint(false /* front */,
|
||||
@@ -519,11 +533,11 @@ public class ScrimControllerTest extends SysuiTestCase {
|
||||
Assert.assertEquals(ScrimController.TRANSPARENT,
|
||||
mScrimInFront.getViewAlpha(), 0.0f);
|
||||
// Back scrim should be visible
|
||||
Assert.assertEquals(ScrimController.BLUR_SCRIM_ALPHA,
|
||||
Assert.assertEquals(ScrimController.BUSY_SCRIM_ALPHA,
|
||||
mScrimBehind.getViewAlpha(), 0.0f);
|
||||
// Bubble scrim should be visible
|
||||
Assert.assertEquals(ScrimController.BLUR_SCRIM_ALPHA,
|
||||
mScrimBehind.getViewAlpha(), 0.0f);
|
||||
Assert.assertEquals(ScrimController.BUBBLE_SCRIM_ALPHA,
|
||||
mScrimForBubble.getViewAlpha(), 0.0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -563,6 +577,15 @@ public class ScrimControllerTest extends SysuiTestCase {
|
||||
mScrimBehindAlpha, mScrimBehind.getViewAlpha(), 0.01f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void qsExpansion() {
|
||||
reset(mScrimBehind);
|
||||
mScrimController.setQsExpansion(1f);
|
||||
finishAnimationsImmediately();
|
||||
|
||||
assertScrimAlpha(TRANSPARENT, OPAQUE, TRANSPARENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void panelExpansionAffectsAlpha() {
|
||||
mScrimController.setPanelExpansion(0f);
|
||||
@@ -888,7 +911,7 @@ public class ScrimControllerTest extends SysuiTestCase {
|
||||
HashSet<ScrimState> regularStates = new HashSet<>(Arrays.asList(
|
||||
ScrimState.UNINITIALIZED, ScrimState.KEYGUARD, ScrimState.BOUNCER,
|
||||
ScrimState.BOUNCER_SCRIMMED, ScrimState.BRIGHTNESS_MIRROR, ScrimState.UNLOCKED,
|
||||
ScrimState.BUBBLE_EXPANDED));
|
||||
ScrimState.BUBBLE_EXPANDED, ScrimState.SHADE_LOCKED));
|
||||
|
||||
for (ScrimState state : ScrimState.values()) {
|
||||
if (!lowPowerModeStates.contains(state) && !regularStates.contains(state)) {
|
||||
|
||||
Reference in New Issue
Block a user