Begin Migrate NSSL
When Flags.migrate_nssl is enabled, move NSSL to be a sibling of both keyguard_root_view and notification_panel. More work is needed for touch handling before moving to teamfood. Also, there were 2 files testing NotificationsQSContainerController... consolidated them, and then split them to a "legacy" one where the new flag is off, and the normal one where the flag is true. Bug: 287644451 Fixes: 288237173 Test: atest NotificationStackScrollLayoutControllerTest NotificationsQSContainerControllerTest SharedNotificationContainerInteractorTest SharedNotificationContainerViewModelTest Change-Id: I291f460d6d5cfd95e338394accca37802b19bbfe
This commit is contained in:
@@ -76,6 +76,13 @@
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<!-- Shared container for the notification stack. Can be positioned by either
|
||||
the keyguard_root_view or notification_panel -->
|
||||
<com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
|
||||
android:id="@+id/shared_notification_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<include layout="@layout/brightness_mirror_container" />
|
||||
|
||||
<com.android.systemui.scrim.ScrimView
|
||||
|
||||
@@ -211,6 +211,7 @@
|
||||
<item type="id" name="keyguard_indication_area" />
|
||||
<item type="id" name="keyguard_indication_text" />
|
||||
<item type="id" name="keyguard_indication_text_bottom" />
|
||||
<item type="id" name="nssl_guideline" />
|
||||
<item type="id" name="lock_icon" />
|
||||
<item type="id" name="lock_icon_bg" />
|
||||
</resources>
|
||||
|
||||
@@ -31,6 +31,9 @@ import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManagerCommand
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
|
||||
import com.android.systemui.shade.NotificationShadeWindowView
|
||||
import com.android.systemui.statusbar.KeyguardIndicationController
|
||||
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
|
||||
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
|
||||
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.DisposableHandle
|
||||
|
||||
@@ -40,7 +43,9 @@ class KeyguardViewConfigurator
|
||||
@Inject
|
||||
constructor(
|
||||
private val keyguardRootView: KeyguardRootView,
|
||||
private val sharedNotificationContainer: SharedNotificationContainer,
|
||||
private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
|
||||
private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
|
||||
private val notificationShadeWindowView: NotificationShadeWindowView,
|
||||
private val featureFlags: FeatureFlags,
|
||||
private val indicationController: KeyguardIndicationController,
|
||||
@@ -55,10 +60,28 @@ constructor(
|
||||
notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
|
||||
bindIndicationArea(notificationPanel)
|
||||
bindLockIconView(notificationPanel)
|
||||
setupNotificationStackScrollLayout(notificationPanel)
|
||||
|
||||
keyguardLayoutManager.layoutViews()
|
||||
keyguardLayoutManagerCommandListener.start()
|
||||
}
|
||||
|
||||
fun setupNotificationStackScrollLayout(legacyParent: ViewGroup) {
|
||||
if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
|
||||
// This moves the existing NSSL view to a different parent, as the controller is a
|
||||
// singleton and recreating it has other bad side effects
|
||||
val nssl =
|
||||
legacyParent.requireViewById<View>(R.id.notification_stack_scroller).also {
|
||||
(it.getParent() as ViewGroup).removeView(it)
|
||||
}
|
||||
sharedNotificationContainer.addNotificationStackScrollLayout(nssl)
|
||||
SharedNotificationContainerBinder.bind(
|
||||
sharedNotificationContainer,
|
||||
sharedNotificationContainerViewModel
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun bindIndicationArea(legacyParent: ViewGroup) {
|
||||
indicationAreaHandle?.dispose()
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.fragments.FragmentService
|
||||
import com.android.systemui.navigationbar.NavigationModeController
|
||||
import com.android.systemui.plugins.qs.QS
|
||||
@@ -36,6 +38,7 @@ import com.android.systemui.plugins.qs.QSContainerController
|
||||
import com.android.systemui.recents.OverviewProxyService
|
||||
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
|
||||
import com.android.systemui.shared.system.QuickStepContract
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
|
||||
import com.android.systemui.util.LargeScreenUtils
|
||||
import com.android.systemui.util.ViewController
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||
@@ -54,7 +57,10 @@ class NotificationsQSContainerController @Inject constructor(
|
||||
private val shadeHeaderController: ShadeHeaderController,
|
||||
private val shadeExpansionStateManager: ShadeExpansionStateManager,
|
||||
private val fragmentService: FragmentService,
|
||||
@Main private val delayableExecutor: DelayableExecutor
|
||||
@Main private val delayableExecutor: DelayableExecutor,
|
||||
private val featureFlags: FeatureFlags,
|
||||
private val
|
||||
notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
|
||||
) : ViewController<NotificationsQuickSettingsContainer>(view), QSContainerController {
|
||||
|
||||
private var qsExpanded = false
|
||||
@@ -118,6 +124,9 @@ class NotificationsQSContainerController @Inject constructor(
|
||||
isGestureNavigation = QuickStepContract.isGesturalMode(mode)
|
||||
}
|
||||
isGestureNavigation = QuickStepContract.isGesturalMode(currentMode)
|
||||
|
||||
mView.setStackScroller(notificationStackScrollLayoutController.getView())
|
||||
mView.setMigratingNSSL(featureFlags.isEnabled(Flags.MIGRATE_NSSL))
|
||||
}
|
||||
|
||||
public override fun onViewAttached() {
|
||||
@@ -254,14 +263,17 @@ class NotificationsQSContainerController @Inject constructor(
|
||||
}
|
||||
|
||||
private fun setNotificationsConstraints(constraintSet: ConstraintSet) {
|
||||
if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
|
||||
return
|
||||
}
|
||||
val startConstraintId = if (splitShadeEnabled) R.id.qs_edge_guideline else PARENT_ID
|
||||
val nsslId = R.id.notification_stack_scroller
|
||||
constraintSet.apply {
|
||||
connect(R.id.notification_stack_scroller, START, startConstraintId, START)
|
||||
setMargin(R.id.notification_stack_scroller, START,
|
||||
if (splitShadeEnabled) 0 else panelMarginHorizontal)
|
||||
setMargin(R.id.notification_stack_scroller, END, panelMarginHorizontal)
|
||||
setMargin(R.id.notification_stack_scroller, TOP, topMargin)
|
||||
setMargin(R.id.notification_stack_scroller, BOTTOM, notificationsBottomMargin)
|
||||
connect(nsslId, START, startConstraintId, START)
|
||||
setMargin(nsslId, START, if (splitShadeEnabled) 0 else panelMarginHorizontal)
|
||||
setMargin(nsslId, END, panelMarginHorizontal)
|
||||
setMargin(nsslId, TOP, topMargin)
|
||||
setMargin(nsslId, BOTTOM, notificationsBottomMargin)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup.MarginLayoutParams;
|
||||
import android.view.WindowInsets;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -56,6 +57,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
|
||||
private QS mQs;
|
||||
private View mQSContainer;
|
||||
private int mLastQSPaddingBottom;
|
||||
private boolean mIsMigratingNSSL;
|
||||
|
||||
/**
|
||||
* These are used to compute the bounding box containing the shade and the notification scrim,
|
||||
@@ -75,10 +77,13 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mQsFrame = findViewById(R.id.qs_frame);
|
||||
mStackScroller = findViewById(R.id.notification_stack_scroller);
|
||||
mKeyguardStatusBar = findViewById(R.id.keyguard_header);
|
||||
}
|
||||
|
||||
void setStackScroller(View stackScroller) {
|
||||
mStackScroller = stackScroller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFragmentViewCreated(String tag, Fragment fragment) {
|
||||
mQs = (QS) fragment;
|
||||
@@ -108,7 +113,7 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
|
||||
}
|
||||
|
||||
public void setNotificationsMarginBottom(int margin) {
|
||||
LayoutParams params = (LayoutParams) mStackScroller.getLayoutParams();
|
||||
MarginLayoutParams params = (MarginLayoutParams) mStackScroller.getLayoutParams();
|
||||
params.bottomMargin = margin;
|
||||
mStackScroller.setLayoutParams(params);
|
||||
}
|
||||
@@ -173,8 +178,15 @@ public class NotificationsQuickSettingsContainer extends ConstraintLayout
|
||||
super.dispatchDraw(canvas);
|
||||
}
|
||||
|
||||
void setMigratingNSSL(boolean isMigrating) {
|
||||
mIsMigratingNSSL = isMigrating;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
|
||||
if (mIsMigratingNSSL) {
|
||||
return super.drawChild(canvas, child, drawingTime);
|
||||
}
|
||||
int layoutIndex = mLayoutDrawingOrder.indexOf(child);
|
||||
if (layoutIndex >= 0) {
|
||||
return super.drawChild(canvas, mDrawingOrderedChildren.get(layoutIndex), drawingTime);
|
||||
|
||||
@@ -49,6 +49,7 @@ import com.android.systemui.statusbar.NotificationShelfController
|
||||
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent
|
||||
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
|
||||
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
|
||||
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
|
||||
import com.android.systemui.statusbar.phone.StatusBarLocation
|
||||
import com.android.systemui.statusbar.phone.StatusIconContainer
|
||||
@@ -203,6 +204,14 @@ abstract class ShadeModule {
|
||||
return notificationShadeWindowView.findViewById(R.id.keyguard_root_view)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@SysUISingleton
|
||||
fun providesSharedNotificationContainer(
|
||||
notificationShadeWindowView: NotificationShadeWindowView,
|
||||
): SharedNotificationContainer {
|
||||
return notificationShadeWindowView.findViewById(R.id.shared_notification_container)
|
||||
}
|
||||
|
||||
// TODO(b/277762009): Only allow this view's controller to inject the view. See above.
|
||||
@Provides
|
||||
@SysUISingleton
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.statusbar.notification.stack.domain.interactor
|
||||
|
||||
import android.content.Context
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
|
||||
/** Encapsulates business-logic specifically related to the shared notification stack container. */
|
||||
class SharedNotificationContainerInteractor
|
||||
@Inject
|
||||
constructor(
|
||||
configurationRepository: ConfigurationRepository,
|
||||
private val context: Context,
|
||||
) {
|
||||
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
|
||||
configurationRepository.onAnyConfigurationChange
|
||||
.onStart { emit(Unit) }
|
||||
.map { _ ->
|
||||
with(context.resources) {
|
||||
ConfigurationBasedDimensions(
|
||||
useSplitShade = getBoolean(R.bool.config_use_split_notification_shade),
|
||||
useLargeScreenHeader =
|
||||
getBoolean(R.bool.config_use_large_screen_shade_header),
|
||||
marginHorizontal =
|
||||
getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal),
|
||||
marginBottom =
|
||||
getDimensionPixelSize(R.dimen.notification_panel_margin_bottom),
|
||||
marginTop = getDimensionPixelSize(R.dimen.notification_panel_margin_top),
|
||||
marginTopLargeScreen =
|
||||
getDimensionPixelSize(R.dimen.large_screen_shade_header_height),
|
||||
)
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
data class ConfigurationBasedDimensions(
|
||||
val useSplitShade: Boolean,
|
||||
val useLargeScreenHeader: Boolean,
|
||||
val marginHorizontal: Int,
|
||||
val marginBottom: Int,
|
||||
val marginTop: Int,
|
||||
val marginTopLargeScreen: Int,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.statusbar.notification.stack.ui.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
|
||||
import androidx.constraintlayout.widget.ConstraintSet.END
|
||||
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
|
||||
import androidx.constraintlayout.widget.ConstraintSet.START
|
||||
import androidx.constraintlayout.widget.ConstraintSet.TOP
|
||||
import androidx.constraintlayout.widget.ConstraintSet.VERTICAL
|
||||
import com.android.systemui.R
|
||||
|
||||
/**
|
||||
* Container for the stack scroller, so that the bounds can be externally specified, such as from
|
||||
* the keyguard or shade scenes.
|
||||
*/
|
||||
class SharedNotificationContainer(
|
||||
context: Context,
|
||||
private val attrs: AttributeSet?,
|
||||
) :
|
||||
ConstraintLayout(
|
||||
context,
|
||||
attrs,
|
||||
) {
|
||||
|
||||
private val baseConstraintSet = ConstraintSet()
|
||||
|
||||
init {
|
||||
baseConstraintSet.apply {
|
||||
create(R.id.nssl_guideline, VERTICAL)
|
||||
setGuidelinePercent(R.id.nssl_guideline, 0.5f)
|
||||
}
|
||||
baseConstraintSet.applyTo(this)
|
||||
}
|
||||
|
||||
fun addNotificationStackScrollLayout(nssl: View) {
|
||||
addView(nssl)
|
||||
}
|
||||
|
||||
fun updateConstraints(
|
||||
useSplitShade: Boolean,
|
||||
marginStart: Int,
|
||||
marginTop: Int,
|
||||
marginEnd: Int,
|
||||
marginBottom: Int
|
||||
) {
|
||||
val constraintSet = ConstraintSet()
|
||||
constraintSet.clone(baseConstraintSet)
|
||||
|
||||
val startConstraintId =
|
||||
if (useSplitShade) {
|
||||
R.id.nssl_guideline
|
||||
} else {
|
||||
PARENT_ID
|
||||
}
|
||||
val nsslId = R.id.notification_stack_scroller
|
||||
constraintSet.apply {
|
||||
connect(nsslId, START, startConstraintId, START)
|
||||
connect(nsslId, END, PARENT_ID, END)
|
||||
connect(nsslId, BOTTOM, PARENT_ID, BOTTOM)
|
||||
connect(nsslId, TOP, PARENT_ID, TOP)
|
||||
setMargin(nsslId, START, marginStart)
|
||||
setMargin(nsslId, END, marginEnd)
|
||||
setMargin(nsslId, TOP, marginTop)
|
||||
setMargin(nsslId, BOTTOM, marginBottom)
|
||||
}
|
||||
constraintSet.applyTo(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.statusbar.notification.stack.ui.viewbinder
|
||||
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
|
||||
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** Binds the shared notification container to its view-model. */
|
||||
object SharedNotificationContainerBinder {
|
||||
|
||||
@JvmStatic
|
||||
fun bind(
|
||||
view: SharedNotificationContainer,
|
||||
viewModel: SharedNotificationContainerViewModel,
|
||||
) {
|
||||
view.repeatWhenAttached {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
viewModel.configurationBasedDimensions.collect {
|
||||
view.updateConstraints(
|
||||
useSplitShade = it.useSplitShade,
|
||||
marginStart = it.marginStart,
|
||||
marginTop = it.marginTop,
|
||||
marginEnd = it.marginEnd,
|
||||
marginBottom = it.marginBottom,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.statusbar.notification.stack.ui.viewmodel
|
||||
|
||||
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/** View-model for the shared notification container */
|
||||
class SharedNotificationContainerViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
interactor: SharedNotificationContainerInteractor,
|
||||
) {
|
||||
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
|
||||
interactor.configurationBasedDimensions
|
||||
.map {
|
||||
ConfigurationBasedDimensions(
|
||||
marginStart = if (it.useSplitShade) 0 else it.marginHorizontal,
|
||||
marginEnd = it.marginHorizontal,
|
||||
marginBottom = it.marginBottom,
|
||||
marginTop =
|
||||
if (it.useLargeScreenHeader) it.marginTopLargeScreen else it.marginTop,
|
||||
useSplitShade = it.useSplitShade,
|
||||
)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
data class ConfigurationBasedDimensions(
|
||||
val marginStart: Int,
|
||||
val marginTop: Int,
|
||||
val marginEnd: Int,
|
||||
val marginBottom: Int,
|
||||
val useSplitShade: Boolean,
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.shade
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import android.testing.TestableResources
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowInsets
|
||||
@@ -12,6 +29,8 @@ import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.flags.FakeFeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.fragments.FragmentHostManager
|
||||
import com.android.systemui.fragments.FragmentService
|
||||
import com.android.systemui.navigationbar.NavigationModeController
|
||||
@@ -19,7 +38,10 @@ import com.android.systemui.navigationbar.NavigationModeController.ModeChangedLi
|
||||
import com.android.systemui.plugins.qs.QS
|
||||
import com.android.systemui.recents.OverviewProxyService
|
||||
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.mockito.capture
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.util.function.Consumer
|
||||
@@ -40,77 +62,63 @@ import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.reset
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
|
||||
@SmallTest
|
||||
/** Uses Flags.MIGRATE_NSSL set to false. If all goes well, this set of tests will be deleted. */
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@TestableLooper.RunWithLooper
|
||||
class NotificationQSContainerControllerTest : SysuiTestCase() {
|
||||
@SmallTest
|
||||
class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() {
|
||||
|
||||
companion object {
|
||||
const val STABLE_INSET_BOTTOM = 100
|
||||
const val CUTOUT_HEIGHT = 50
|
||||
const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
|
||||
const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
|
||||
const val NOTIFICATIONS_MARGIN = 50
|
||||
const val SCRIM_MARGIN = 10
|
||||
const val FOOTER_ACTIONS_INSET = 2
|
||||
const val FOOTER_ACTIONS_PADDING = 2
|
||||
const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
|
||||
const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
|
||||
}
|
||||
@Mock lateinit var view: NotificationsQuickSettingsContainer
|
||||
@Mock lateinit var navigationModeController: NavigationModeController
|
||||
@Mock lateinit var overviewProxyService: OverviewProxyService
|
||||
@Mock lateinit var shadeHeaderController: ShadeHeaderController
|
||||
@Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
|
||||
@Mock lateinit var fragmentService: FragmentService
|
||||
@Mock lateinit var fragmentHostManager: FragmentHostManager
|
||||
@Mock
|
||||
lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController
|
||||
|
||||
@Mock
|
||||
private lateinit var navigationModeController: NavigationModeController
|
||||
@Mock
|
||||
private lateinit var overviewProxyService: OverviewProxyService
|
||||
@Mock
|
||||
private lateinit var notificationsQSContainer: NotificationsQuickSettingsContainer
|
||||
@Mock
|
||||
private lateinit var mShadeHeaderController: ShadeHeaderController
|
||||
@Mock
|
||||
private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
|
||||
@Mock
|
||||
private lateinit var fragmentService: FragmentService
|
||||
@Mock
|
||||
private lateinit var fragmentHostManager: FragmentHostManager
|
||||
@Captor
|
||||
lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
|
||||
@Captor
|
||||
lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
|
||||
@Captor
|
||||
lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
|
||||
@Captor
|
||||
lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
|
||||
@Captor
|
||||
lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
|
||||
@Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
|
||||
@Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
|
||||
@Captor lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
|
||||
@Captor lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
|
||||
@Captor lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
|
||||
|
||||
private lateinit var controller: NotificationsQSContainerController
|
||||
lateinit var underTest: NotificationsQSContainerController
|
||||
|
||||
private lateinit var fakeResources: TestableResources
|
||||
private lateinit var featureFlags: FakeFeatureFlags
|
||||
private lateinit var navigationModeCallback: ModeChangedListener
|
||||
private lateinit var taskbarVisibilityCallback: OverviewProxyListener
|
||||
private lateinit var windowInsetsCallback: Consumer<WindowInsets>
|
||||
private lateinit var delayableExecutor: FakeExecutor
|
||||
private lateinit var fakeSystemClock: FakeSystemClock
|
||||
private lateinit var delayableExecutor: FakeExecutor
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
mContext.ensureTestableResources()
|
||||
whenever(notificationsQSContainer.context).thenReturn(mContext)
|
||||
whenever(notificationsQSContainer.resources).thenReturn(mContext.resources)
|
||||
whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
|
||||
fakeSystemClock = FakeSystemClock()
|
||||
delayableExecutor = FakeExecutor(fakeSystemClock)
|
||||
featureFlags = FakeFeatureFlags().apply { set(Flags.MIGRATE_NSSL, false) }
|
||||
mContext.ensureTestableResources()
|
||||
whenever(view.context).thenReturn(mContext)
|
||||
whenever(view.resources).thenReturn(mContext.resources)
|
||||
|
||||
controller = NotificationsQSContainerController(
|
||||
notificationsQSContainer,
|
||||
whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
|
||||
|
||||
underTest =
|
||||
NotificationsQSContainerController(
|
||||
view,
|
||||
navigationModeController,
|
||||
overviewProxyService,
|
||||
mShadeHeaderController,
|
||||
shadeHeaderController,
|
||||
shadeExpansionStateManager,
|
||||
fragmentService,
|
||||
delayableExecutor
|
||||
)
|
||||
delayableExecutor,
|
||||
featureFlags,
|
||||
notificationStackScrollLayoutController,
|
||||
)
|
||||
|
||||
overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
|
||||
overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN)
|
||||
@@ -118,38 +126,72 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
|
||||
overrideResource(R.dimen.qs_footer_actions_bottom_padding, FOOTER_ACTIONS_PADDING)
|
||||
overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
|
||||
whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
|
||||
.thenReturn(GESTURES_NAVIGATION)
|
||||
.thenReturn(GESTURES_NAVIGATION)
|
||||
doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
|
||||
doNothing().`when`(notificationsQSContainer)
|
||||
.setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
|
||||
doNothing().`when`(notificationsQSContainer).applyConstraints(constraintSetCaptor.capture())
|
||||
doNothing().`when`(notificationsQSContainer)
|
||||
.addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
|
||||
controller.init()
|
||||
attachStateListenerCaptor.value.onViewAttachedToWindow(notificationsQSContainer)
|
||||
doNothing().`when`(view).setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
|
||||
doNothing().`when`(view).applyConstraints(constraintSetCaptor.capture())
|
||||
doNothing().`when`(view).addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
|
||||
underTest.init()
|
||||
attachStateListenerCaptor.value.onViewAttachedToWindow(view)
|
||||
|
||||
navigationModeCallback = navigationModeCaptor.value
|
||||
taskbarVisibilityCallback = taskbarVisibilityCaptor.value
|
||||
windowInsetsCallback = windowInsetsCallbackCaptor.value
|
||||
|
||||
Mockito.clearInvocations(view)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSmallScreen_updateResources_splitShadeHeightIsSet() {
|
||||
overrideResource(R.bool.config_use_large_screen_shade_header, false)
|
||||
overrideResource(R.dimen.qs_header_height, 1)
|
||||
overrideResource(R.dimen.large_screen_shade_header_height, 2)
|
||||
|
||||
underTest.updateResources()
|
||||
|
||||
val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
|
||||
verify(view).applyConstraints(capture(captor))
|
||||
assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
|
||||
overrideResource(R.bool.config_use_large_screen_shade_header, true)
|
||||
overrideResource(R.dimen.qs_header_height, 1)
|
||||
overrideResource(R.dimen.large_screen_shade_header_height, 2)
|
||||
|
||||
underTest.updateResources()
|
||||
|
||||
val captor = ArgumentCaptor.forClass(ConstraintSet::class.java)
|
||||
verify(view).applyConstraints(capture(captor))
|
||||
assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTaskbarVisibleInSplitShade() {
|
||||
enableSplitShade()
|
||||
|
||||
given(taskbarVisible = true,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom())
|
||||
then(expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
|
||||
expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
|
||||
expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
|
||||
given(
|
||||
taskbarVisible = true,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(
|
||||
expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
|
||||
expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
|
||||
expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
|
||||
)
|
||||
|
||||
given(taskbarVisible = true,
|
||||
navigationMode = BUTTONS_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom())
|
||||
then(expectedContainerPadding = STABLE_INSET_BOTTOM,
|
||||
expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
|
||||
expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
|
||||
given(
|
||||
taskbarVisible = true,
|
||||
navigationMode = BUTTONS_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(
|
||||
expectedContainerPadding = STABLE_INSET_BOTTOM,
|
||||
expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
|
||||
expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -157,161 +199,185 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
|
||||
// when taskbar is not visible, it means we're on the home screen
|
||||
enableSplitShade()
|
||||
|
||||
given(taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom())
|
||||
then(expectedContainerPadding = 0,
|
||||
expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(
|
||||
expectedContainerPadding = 0,
|
||||
expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
|
||||
)
|
||||
|
||||
given(taskbarVisible = false,
|
||||
navigationMode = BUTTONS_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom())
|
||||
then(expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
|
||||
expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
|
||||
expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = BUTTONS_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(
|
||||
expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
|
||||
expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
|
||||
expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTaskbarNotVisibleInSplitShadeWithCutout() {
|
||||
enableSplitShade()
|
||||
|
||||
given(taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withCutout())
|
||||
then(expectedContainerPadding = CUTOUT_HEIGHT,
|
||||
expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withCutout()
|
||||
)
|
||||
then(
|
||||
expectedContainerPadding = CUTOUT_HEIGHT,
|
||||
expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
|
||||
)
|
||||
|
||||
given(taskbarVisible = false,
|
||||
navigationMode = BUTTONS_NAVIGATION,
|
||||
insets = windowInsets().withCutout().withStableBottom())
|
||||
then(expectedContainerPadding = 0,
|
||||
expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
|
||||
expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET)
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = BUTTONS_NAVIGATION,
|
||||
insets = windowInsets().withCutout().withStableBottom()
|
||||
)
|
||||
then(
|
||||
expectedContainerPadding = 0,
|
||||
expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
|
||||
expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTaskbarVisibleInSinglePaneShade() {
|
||||
disableSplitShade()
|
||||
|
||||
given(taskbarVisible = true,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom())
|
||||
then(expectedContainerPadding = 0,
|
||||
expectedQsPadding = STABLE_INSET_BOTTOM)
|
||||
given(
|
||||
taskbarVisible = true,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
|
||||
|
||||
given(taskbarVisible = true,
|
||||
navigationMode = BUTTONS_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom())
|
||||
then(expectedContainerPadding = STABLE_INSET_BOTTOM,
|
||||
expectedQsPadding = STABLE_INSET_BOTTOM)
|
||||
given(
|
||||
taskbarVisible = true,
|
||||
navigationMode = BUTTONS_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(
|
||||
expectedContainerPadding = STABLE_INSET_BOTTOM,
|
||||
expectedQsPadding = STABLE_INSET_BOTTOM
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTaskbarNotVisibleInSinglePaneShade() {
|
||||
disableSplitShade()
|
||||
|
||||
given(taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = emptyInsets())
|
||||
given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = emptyInsets())
|
||||
then(expectedContainerPadding = 0)
|
||||
|
||||
given(taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withCutout().withStableBottom())
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withCutout().withStableBottom()
|
||||
)
|
||||
then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
|
||||
|
||||
given(taskbarVisible = false,
|
||||
navigationMode = BUTTONS_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom())
|
||||
then(expectedContainerPadding = 0,
|
||||
expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
|
||||
expectedQsPadding = STABLE_INSET_BOTTOM)
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = BUTTONS_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(
|
||||
expectedContainerPadding = 0,
|
||||
expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
|
||||
expectedQsPadding = STABLE_INSET_BOTTOM
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDetailShowingInSinglePaneShade() {
|
||||
disableSplitShade()
|
||||
controller.setDetailShowing(true)
|
||||
underTest.setDetailShowing(true)
|
||||
|
||||
// always sets spacings to 0
|
||||
given(taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom())
|
||||
then(expectedContainerPadding = 0,
|
||||
expectedNotificationsMargin = 0)
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
|
||||
|
||||
given(taskbarVisible = false,
|
||||
navigationMode = BUTTONS_NAVIGATION,
|
||||
insets = emptyInsets())
|
||||
then(expectedContainerPadding = 0,
|
||||
expectedNotificationsMargin = 0)
|
||||
given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
|
||||
then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDetailShowingInSplitShade() {
|
||||
enableSplitShade()
|
||||
controller.setDetailShowing(true)
|
||||
underTest.setDetailShowing(true)
|
||||
|
||||
given(taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom())
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(expectedContainerPadding = 0)
|
||||
|
||||
// should not influence spacing
|
||||
given(taskbarVisible = false,
|
||||
navigationMode = BUTTONS_NAVIGATION,
|
||||
insets = emptyInsets())
|
||||
given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
|
||||
then(expectedContainerPadding = 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationsMarginBottomIsUpdated() {
|
||||
Mockito.clearInvocations(notificationsQSContainer)
|
||||
Mockito.clearInvocations(view)
|
||||
enableSplitShade()
|
||||
verify(notificationsQSContainer).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
|
||||
verify(view).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
|
||||
|
||||
overrideResource(R.dimen.notification_panel_margin_bottom, 100)
|
||||
disableSplitShade()
|
||||
verify(notificationsQSContainer).setNotificationsMarginBottom(100)
|
||||
verify(view).setNotificationsMarginBottom(100)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSplitShadeLayout_isAlignedToGuideline() {
|
||||
enableSplitShade()
|
||||
controller.updateResources()
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
|
||||
.isEqualTo(R.id.qs_edge_guideline)
|
||||
underTest.updateResources()
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
|
||||
assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
|
||||
.isEqualTo(R.id.qs_edge_guideline)
|
||||
.isEqualTo(R.id.qs_edge_guideline)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSinglePaneLayout_childrenHaveEqualMargins() {
|
||||
disableSplitShade()
|
||||
controller.updateResources()
|
||||
underTest.updateResources()
|
||||
val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
|
||||
val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
|
||||
val notifStartMargin = getConstraintSetLayout(R.id.notification_stack_scroller).startMargin
|
||||
val notifEndMargin = getConstraintSetLayout(R.id.notification_stack_scroller).endMargin
|
||||
assertThat(qsStartMargin == qsEndMargin &&
|
||||
notifStartMargin == notifEndMargin &&
|
||||
qsStartMargin == notifStartMargin
|
||||
).isTrue()
|
||||
assertThat(
|
||||
qsStartMargin == qsEndMargin &&
|
||||
notifStartMargin == notifEndMargin &&
|
||||
qsStartMargin == notifStartMargin
|
||||
)
|
||||
.isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
|
||||
enableSplitShade()
|
||||
controller.updateResources()
|
||||
underTest.updateResources()
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
|
||||
assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startMargin)
|
||||
.isEqualTo(0)
|
||||
.isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
|
||||
enableSplitShade()
|
||||
controller.updateResources()
|
||||
underTest.updateResources()
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(0)
|
||||
}
|
||||
@@ -322,62 +388,64 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
|
||||
val largeScreenHeaderHeight = 100
|
||||
overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
|
||||
|
||||
controller.updateResources()
|
||||
underTest.updateResources()
|
||||
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
|
||||
.isEqualTo(largeScreenHeaderHeight)
|
||||
.isEqualTo(largeScreenHeaderHeight)
|
||||
assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
|
||||
.isEqualTo(largeScreenHeaderHeight)
|
||||
.isEqualTo(largeScreenHeaderHeight)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
|
||||
setSmallScreen()
|
||||
controller.updateResources()
|
||||
underTest.updateResources()
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
|
||||
assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin)
|
||||
.isEqualTo(0)
|
||||
assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).topMargin).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
|
||||
disableSplitShade()
|
||||
controller.updateResources()
|
||||
val notificationPanelMarginHorizontal = context.resources
|
||||
.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
|
||||
underTest.updateResources()
|
||||
val notificationPanelMarginHorizontal =
|
||||
mContext.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin)
|
||||
.isEqualTo(notificationPanelMarginHorizontal)
|
||||
.isEqualTo(notificationPanelMarginHorizontal)
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin)
|
||||
.isEqualTo(notificationPanelMarginHorizontal)
|
||||
.isEqualTo(notificationPanelMarginHorizontal)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSinglePaneShadeLayout_isAlignedToParent() {
|
||||
disableSplitShade()
|
||||
controller.updateResources()
|
||||
underTest.updateResources()
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
|
||||
.isEqualTo(ConstraintSet.PARENT_ID)
|
||||
.isEqualTo(ConstraintSet.PARENT_ID)
|
||||
assertThat(getConstraintSetLayout(R.id.notification_stack_scroller).startToStart)
|
||||
.isEqualTo(ConstraintSet.PARENT_ID)
|
||||
.isEqualTo(ConstraintSet.PARENT_ID)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAllChildrenOfNotificationContainer_haveIds() {
|
||||
// set dimen to 0 to avoid triggering updating bottom spacing
|
||||
overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
|
||||
val container = NotificationsQuickSettingsContainer(context, null)
|
||||
val container = NotificationsQuickSettingsContainer(mContext, null)
|
||||
container.removeAllViews()
|
||||
container.addView(newViewWithId(1))
|
||||
container.addView(newViewWithId(View.NO_ID))
|
||||
val controller = NotificationsQSContainerController(
|
||||
val controller =
|
||||
NotificationsQSContainerController(
|
||||
container,
|
||||
navigationModeController,
|
||||
overviewProxyService,
|
||||
mShadeHeaderController,
|
||||
shadeHeaderController,
|
||||
shadeExpansionStateManager,
|
||||
fragmentService,
|
||||
delayableExecutor
|
||||
)
|
||||
delayableExecutor,
|
||||
featureFlags,
|
||||
notificationStackScrollLayoutController,
|
||||
)
|
||||
controller.updateConstraints()
|
||||
|
||||
assertThat(container.getChildAt(0).id).isEqualTo(1)
|
||||
@@ -388,44 +456,46 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
|
||||
fun testWindowInsetDebounce() {
|
||||
disableSplitShade()
|
||||
|
||||
given(taskbarVisible = false,
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = emptyInsets(),
|
||||
applyImmediately = false)
|
||||
applyImmediately = false
|
||||
)
|
||||
fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
|
||||
windowInsetsCallback.accept(windowInsets().withStableBottom())
|
||||
|
||||
delayableExecutor.advanceClockToLast()
|
||||
delayableExecutor.runAllReady()
|
||||
|
||||
verify(notificationsQSContainer, never()).setQSContainerPaddingBottom(0)
|
||||
verify(notificationsQSContainer).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
|
||||
verify(view, never()).setQSContainerPaddingBottom(0)
|
||||
verify(view).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStartCustomizingWithDuration() {
|
||||
controller.setCustomizerShowing(true, 100L)
|
||||
verify(mShadeHeaderController).startCustomizingAnimation(true, 100L)
|
||||
underTest.setCustomizerShowing(true, 100L)
|
||||
verify(shadeHeaderController).startCustomizingAnimation(true, 100L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEndCustomizingWithDuration() {
|
||||
controller.setCustomizerShowing(true, 0L) // Only tracks changes
|
||||
reset(mShadeHeaderController)
|
||||
underTest.setCustomizerShowing(true, 0L) // Only tracks changes
|
||||
reset(shadeHeaderController)
|
||||
|
||||
controller.setCustomizerShowing(false, 100L)
|
||||
verify(mShadeHeaderController).startCustomizingAnimation(false, 100L)
|
||||
underTest.setCustomizerShowing(false, 100L)
|
||||
verify(shadeHeaderController).startCustomizingAnimation(false, 100L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTagListenerAdded() {
|
||||
verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(notificationsQSContainer))
|
||||
verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(view))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTagListenerRemoved() {
|
||||
attachStateListenerCaptor.value.onViewDetachedFromWindow(notificationsQSContainer)
|
||||
verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(notificationsQSContainer))
|
||||
attachStateListenerCaptor.value.onViewDetachedFromWindow(view)
|
||||
verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(view))
|
||||
}
|
||||
|
||||
private fun disableSplitShade() {
|
||||
@@ -438,7 +508,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
|
||||
|
||||
private fun setSplitShadeEnabled(enabled: Boolean) {
|
||||
overrideResource(R.bool.config_use_split_notification_shade, enabled)
|
||||
controller.updateResources()
|
||||
underTest.updateResources()
|
||||
}
|
||||
|
||||
private fun setSmallScreen() {
|
||||
@@ -459,7 +529,7 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
|
||||
insets: WindowInsets,
|
||||
applyImmediately: Boolean = true
|
||||
) {
|
||||
Mockito.clearInvocations(notificationsQSContainer)
|
||||
Mockito.clearInvocations(view)
|
||||
taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
|
||||
navigationModeCallback.onNavigationModeChanged(navigationMode)
|
||||
windowInsetsCallback.accept(insets)
|
||||
@@ -474,12 +544,10 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
|
||||
expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
|
||||
expectedQsPadding: Int = 0
|
||||
) {
|
||||
verify(notificationsQSContainer)
|
||||
.setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
|
||||
verify(notificationsQSContainer).setNotificationsMarginBottom(expectedNotificationsMargin)
|
||||
verify(notificationsQSContainer)
|
||||
.setQSContainerPaddingBottom(expectedQsPadding)
|
||||
Mockito.clearInvocations(notificationsQSContainer)
|
||||
verify(view).setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
|
||||
verify(view).setNotificationsMarginBottom(expectedNotificationsMargin)
|
||||
verify(view).setQSContainerPaddingBottom(expectedQsPadding)
|
||||
Mockito.clearInvocations(view)
|
||||
}
|
||||
|
||||
private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
|
||||
@@ -503,10 +571,26 @@ class NotificationQSContainerControllerTest : SysuiTestCase() {
|
||||
private fun newViewWithId(id: Int): View {
|
||||
val view = View(mContext)
|
||||
view.id = id
|
||||
val layoutParams = ConstraintLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
val layoutParams =
|
||||
ConstraintLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
// required as cloning ConstraintSet fails if view doesn't have layout params
|
||||
view.layoutParams = layoutParams
|
||||
return view
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val STABLE_INSET_BOTTOM = 100
|
||||
const val CUTOUT_HEIGHT = 50
|
||||
const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
|
||||
const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
|
||||
const val NOTIFICATIONS_MARGIN = 50
|
||||
const val SCRIM_MARGIN = 10
|
||||
const val FOOTER_ACTIONS_INSET = 2
|
||||
const val FOOTER_ACTIONS_PADDING = 2
|
||||
const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
|
||||
const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
|
||||
}
|
||||
}
|
||||
@@ -19,24 +19,47 @@ package com.android.systemui.shade
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import android.testing.TestableResources
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowManagerPolicyConstants
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.flags.FakeFeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.fragments.FragmentHostManager
|
||||
import com.android.systemui.fragments.FragmentService
|
||||
import com.android.systemui.navigationbar.NavigationModeController
|
||||
import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener
|
||||
import com.android.systemui.plugins.qs.QS
|
||||
import com.android.systemui.recents.OverviewProxyService
|
||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener
|
||||
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.mockito.capture
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.util.function.Consumer
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.RETURNS_DEEP_STUBS
|
||||
import org.mockito.Mockito.any
|
||||
import org.mockito.Mockito.anyInt
|
||||
import org.mockito.Mockito.doNothing
|
||||
import org.mockito.Mockito.eq
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.reset
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@@ -51,19 +74,37 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
|
||||
@Mock lateinit var shadeHeaderController: ShadeHeaderController
|
||||
@Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
|
||||
@Mock lateinit var fragmentService: FragmentService
|
||||
@Mock lateinit var fragmentHostManager: FragmentHostManager
|
||||
@Mock
|
||||
lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController
|
||||
|
||||
@Captor lateinit var navigationModeCaptor: ArgumentCaptor<ModeChangedListener>
|
||||
@Captor lateinit var taskbarVisibilityCaptor: ArgumentCaptor<OverviewProxyListener>
|
||||
@Captor lateinit var windowInsetsCallbackCaptor: ArgumentCaptor<Consumer<WindowInsets>>
|
||||
@Captor lateinit var constraintSetCaptor: ArgumentCaptor<ConstraintSet>
|
||||
@Captor lateinit var attachStateListenerCaptor: ArgumentCaptor<View.OnAttachStateChangeListener>
|
||||
|
||||
lateinit var underTest: NotificationsQSContainerController
|
||||
|
||||
private lateinit var fakeResources: TestableResources
|
||||
|
||||
private val delayableExecutor: DelayableExecutor = FakeExecutor(FakeSystemClock())
|
||||
private lateinit var featureFlags: FakeFeatureFlags
|
||||
private lateinit var navigationModeCallback: ModeChangedListener
|
||||
private lateinit var taskbarVisibilityCallback: OverviewProxyListener
|
||||
private lateinit var windowInsetsCallback: Consumer<WindowInsets>
|
||||
private lateinit var fakeSystemClock: FakeSystemClock
|
||||
private lateinit var delayableExecutor: FakeExecutor
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
fakeResources = TestableResources(context.resources)
|
||||
fakeSystemClock = FakeSystemClock()
|
||||
delayableExecutor = FakeExecutor(fakeSystemClock)
|
||||
featureFlags = FakeFeatureFlags().apply { set(Flags.MIGRATE_NSSL, true) }
|
||||
mContext.ensureTestableResources()
|
||||
whenever(view.context).thenReturn(mContext)
|
||||
whenever(view.resources).thenReturn(mContext.resources)
|
||||
|
||||
whenever(view.resources).thenReturn(fakeResources.resources)
|
||||
whenever(fragmentService.getFragmentHostManager(any())).thenReturn(fragmentHostManager)
|
||||
|
||||
underTest =
|
||||
NotificationsQSContainerController(
|
||||
@@ -74,16 +115,36 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
|
||||
shadeExpansionStateManager,
|
||||
fragmentService,
|
||||
delayableExecutor,
|
||||
featureFlags,
|
||||
notificationStackScrollLayoutController,
|
||||
)
|
||||
|
||||
overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN)
|
||||
overrideResource(R.dimen.notification_panel_margin_bottom, NOTIFICATIONS_MARGIN)
|
||||
overrideResource(R.bool.config_use_split_notification_shade, false)
|
||||
overrideResource(R.dimen.qs_footer_actions_bottom_padding, FOOTER_ACTIONS_PADDING)
|
||||
overrideResource(R.dimen.qs_footer_action_inset, FOOTER_ACTIONS_INSET)
|
||||
whenever(navigationModeController.addListener(navigationModeCaptor.capture()))
|
||||
.thenReturn(GESTURES_NAVIGATION)
|
||||
doNothing().`when`(overviewProxyService).addCallback(taskbarVisibilityCaptor.capture())
|
||||
doNothing().`when`(view).setInsetsChangedListener(windowInsetsCallbackCaptor.capture())
|
||||
doNothing().`when`(view).applyConstraints(constraintSetCaptor.capture())
|
||||
doNothing().`when`(view).addOnAttachStateChangeListener(attachStateListenerCaptor.capture())
|
||||
underTest.init()
|
||||
attachStateListenerCaptor.value.onViewAttachedToWindow(view)
|
||||
|
||||
navigationModeCallback = navigationModeCaptor.value
|
||||
taskbarVisibilityCallback = taskbarVisibilityCaptor.value
|
||||
windowInsetsCallback = windowInsetsCallbackCaptor.value
|
||||
|
||||
Mockito.clearInvocations(view)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSmallScreen_updateResources_splitShadeHeightIsSet() {
|
||||
with(fakeResources) {
|
||||
addOverride(R.bool.config_use_large_screen_shade_header, false)
|
||||
addOverride(R.dimen.qs_header_height, 1)
|
||||
addOverride(R.dimen.large_screen_shade_header_height, 2)
|
||||
}
|
||||
overrideResource(R.bool.config_use_large_screen_shade_header, false)
|
||||
overrideResource(R.dimen.qs_header_height, 1)
|
||||
overrideResource(R.dimen.large_screen_shade_header_height, 2)
|
||||
|
||||
underTest.updateResources()
|
||||
|
||||
@@ -94,11 +155,9 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testLargeScreen_updateResources_splitShadeHeightIsSet() {
|
||||
with(fakeResources) {
|
||||
addOverride(R.bool.config_use_large_screen_shade_header, true)
|
||||
addOverride(R.dimen.qs_header_height, 1)
|
||||
addOverride(R.dimen.large_screen_shade_header_height, 2)
|
||||
}
|
||||
overrideResource(R.bool.config_use_large_screen_shade_header, true)
|
||||
overrideResource(R.dimen.qs_header_height, 1)
|
||||
overrideResource(R.dimen.large_screen_shade_header_height, 2)
|
||||
|
||||
underTest.updateResources()
|
||||
|
||||
@@ -106,4 +165,415 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() {
|
||||
verify(view).applyConstraints(capture(captor))
|
||||
assertThat(captor.value.getHeight(R.id.split_shade_status_bar)).isEqualTo(2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTaskbarVisibleInSplitShade() {
|
||||
enableSplitShade()
|
||||
|
||||
given(
|
||||
taskbarVisible = true,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(
|
||||
expectedContainerPadding = 0, // taskbar should disappear when shade is expanded
|
||||
expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
|
||||
expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
|
||||
)
|
||||
|
||||
given(
|
||||
taskbarVisible = true,
|
||||
navigationMode = BUTTONS_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(
|
||||
expectedContainerPadding = STABLE_INSET_BOTTOM,
|
||||
expectedNotificationsMargin = NOTIFICATIONS_MARGIN,
|
||||
expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTaskbarNotVisibleInSplitShade() {
|
||||
// when taskbar is not visible, it means we're on the home screen
|
||||
enableSplitShade()
|
||||
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(
|
||||
expectedContainerPadding = 0,
|
||||
expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
|
||||
)
|
||||
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = BUTTONS_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(
|
||||
expectedContainerPadding = 0, // qs goes full height as it's not obscuring nav buttons
|
||||
expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
|
||||
expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTaskbarNotVisibleInSplitShadeWithCutout() {
|
||||
enableSplitShade()
|
||||
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withCutout()
|
||||
)
|
||||
then(
|
||||
expectedContainerPadding = CUTOUT_HEIGHT,
|
||||
expectedQsPadding = NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
|
||||
)
|
||||
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = BUTTONS_NAVIGATION,
|
||||
insets = windowInsets().withCutout().withStableBottom()
|
||||
)
|
||||
then(
|
||||
expectedContainerPadding = 0,
|
||||
expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
|
||||
expectedQsPadding = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN - QS_PADDING_OFFSET
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTaskbarVisibleInSinglePaneShade() {
|
||||
disableSplitShade()
|
||||
|
||||
given(
|
||||
taskbarVisible = true,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(expectedContainerPadding = 0, expectedQsPadding = STABLE_INSET_BOTTOM)
|
||||
|
||||
given(
|
||||
taskbarVisible = true,
|
||||
navigationMode = BUTTONS_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(
|
||||
expectedContainerPadding = STABLE_INSET_BOTTOM,
|
||||
expectedQsPadding = STABLE_INSET_BOTTOM
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTaskbarNotVisibleInSinglePaneShade() {
|
||||
disableSplitShade()
|
||||
|
||||
given(taskbarVisible = false, navigationMode = GESTURES_NAVIGATION, insets = emptyInsets())
|
||||
then(expectedContainerPadding = 0)
|
||||
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withCutout().withStableBottom()
|
||||
)
|
||||
then(expectedContainerPadding = CUTOUT_HEIGHT, expectedQsPadding = STABLE_INSET_BOTTOM)
|
||||
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = BUTTONS_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(
|
||||
expectedContainerPadding = 0,
|
||||
expectedNotificationsMargin = STABLE_INSET_BOTTOM + NOTIFICATIONS_MARGIN,
|
||||
expectedQsPadding = STABLE_INSET_BOTTOM
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDetailShowingInSinglePaneShade() {
|
||||
disableSplitShade()
|
||||
underTest.setDetailShowing(true)
|
||||
|
||||
// always sets spacings to 0
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
|
||||
|
||||
given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
|
||||
then(expectedContainerPadding = 0, expectedNotificationsMargin = 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDetailShowingInSplitShade() {
|
||||
enableSplitShade()
|
||||
underTest.setDetailShowing(true)
|
||||
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = windowInsets().withStableBottom()
|
||||
)
|
||||
then(expectedContainerPadding = 0)
|
||||
|
||||
// should not influence spacing
|
||||
given(taskbarVisible = false, navigationMode = BUTTONS_NAVIGATION, insets = emptyInsets())
|
||||
then(expectedContainerPadding = 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationsMarginBottomIsUpdated() {
|
||||
Mockito.clearInvocations(view)
|
||||
enableSplitShade()
|
||||
verify(view).setNotificationsMarginBottom(NOTIFICATIONS_MARGIN)
|
||||
|
||||
overrideResource(R.dimen.notification_panel_margin_bottom, 100)
|
||||
disableSplitShade()
|
||||
verify(view).setNotificationsMarginBottom(100)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSplitShadeLayout_isAlignedToGuideline() {
|
||||
enableSplitShade()
|
||||
underTest.updateResources()
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSinglePaneLayout_childrenHaveEqualMargins() {
|
||||
disableSplitShade()
|
||||
underTest.updateResources()
|
||||
val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
|
||||
val qsEndMargin = getConstraintSetLayout(R.id.qs_frame).endMargin
|
||||
assertThat(qsStartMargin == qsEndMargin).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
|
||||
enableSplitShade()
|
||||
underTest.updateResources()
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSplitShadeLayout_qsFrameHasHorizontalMarginsOfZero() {
|
||||
enableSplitShade()
|
||||
underTest.updateResources()
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLargeScreenLayout_qsAndNotifsTopMarginIsOfHeaderHeight() {
|
||||
setLargeScreen()
|
||||
val largeScreenHeaderHeight = 100
|
||||
overrideResource(R.dimen.large_screen_shade_header_height, largeScreenHeaderHeight)
|
||||
|
||||
underTest.updateResources()
|
||||
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin)
|
||||
.isEqualTo(largeScreenHeaderHeight)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
|
||||
setSmallScreen()
|
||||
underTest.updateResources()
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSinglePaneShadeLayout_qsFrameHasHorizontalMarginsSetToCorrectValue() {
|
||||
disableSplitShade()
|
||||
underTest.updateResources()
|
||||
val notificationPanelMarginHorizontal =
|
||||
mContext.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin)
|
||||
.isEqualTo(notificationPanelMarginHorizontal)
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).startMargin)
|
||||
.isEqualTo(notificationPanelMarginHorizontal)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSinglePaneShadeLayout_isAlignedToParent() {
|
||||
disableSplitShade()
|
||||
underTest.updateResources()
|
||||
assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
|
||||
.isEqualTo(ConstraintSet.PARENT_ID)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAllChildrenOfNotificationContainer_haveIds() {
|
||||
// set dimen to 0 to avoid triggering updating bottom spacing
|
||||
overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, 0)
|
||||
val container = NotificationsQuickSettingsContainer(mContext, null)
|
||||
container.removeAllViews()
|
||||
container.addView(newViewWithId(1))
|
||||
container.addView(newViewWithId(View.NO_ID))
|
||||
val controller =
|
||||
NotificationsQSContainerController(
|
||||
container,
|
||||
navigationModeController,
|
||||
overviewProxyService,
|
||||
shadeHeaderController,
|
||||
shadeExpansionStateManager,
|
||||
fragmentService,
|
||||
delayableExecutor,
|
||||
featureFlags,
|
||||
notificationStackScrollLayoutController,
|
||||
)
|
||||
controller.updateConstraints()
|
||||
|
||||
assertThat(container.getChildAt(0).id).isEqualTo(1)
|
||||
assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWindowInsetDebounce() {
|
||||
disableSplitShade()
|
||||
|
||||
given(
|
||||
taskbarVisible = false,
|
||||
navigationMode = GESTURES_NAVIGATION,
|
||||
insets = emptyInsets(),
|
||||
applyImmediately = false
|
||||
)
|
||||
fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2)
|
||||
windowInsetsCallback.accept(windowInsets().withStableBottom())
|
||||
|
||||
delayableExecutor.advanceClockToLast()
|
||||
delayableExecutor.runAllReady()
|
||||
|
||||
verify(view, never()).setQSContainerPaddingBottom(0)
|
||||
verify(view).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStartCustomizingWithDuration() {
|
||||
underTest.setCustomizerShowing(true, 100L)
|
||||
verify(shadeHeaderController).startCustomizingAnimation(true, 100L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEndCustomizingWithDuration() {
|
||||
underTest.setCustomizerShowing(true, 0L) // Only tracks changes
|
||||
reset(shadeHeaderController)
|
||||
|
||||
underTest.setCustomizerShowing(false, 100L)
|
||||
verify(shadeHeaderController).startCustomizingAnimation(false, 100L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTagListenerAdded() {
|
||||
verify(fragmentHostManager).addTagListener(eq(QS.TAG), eq(view))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTagListenerRemoved() {
|
||||
attachStateListenerCaptor.value.onViewDetachedFromWindow(view)
|
||||
verify(fragmentHostManager).removeTagListener(eq(QS.TAG), eq(view))
|
||||
}
|
||||
|
||||
private fun disableSplitShade() {
|
||||
setSplitShadeEnabled(false)
|
||||
}
|
||||
|
||||
private fun enableSplitShade() {
|
||||
setSplitShadeEnabled(true)
|
||||
}
|
||||
|
||||
private fun setSplitShadeEnabled(enabled: Boolean) {
|
||||
overrideResource(R.bool.config_use_split_notification_shade, enabled)
|
||||
underTest.updateResources()
|
||||
}
|
||||
|
||||
private fun setSmallScreen() {
|
||||
setLargeScreenEnabled(false)
|
||||
}
|
||||
|
||||
private fun setLargeScreen() {
|
||||
setLargeScreenEnabled(true)
|
||||
}
|
||||
|
||||
private fun setLargeScreenEnabled(enabled: Boolean) {
|
||||
overrideResource(R.bool.config_use_large_screen_shade_header, enabled)
|
||||
}
|
||||
|
||||
private fun given(
|
||||
taskbarVisible: Boolean,
|
||||
navigationMode: Int,
|
||||
insets: WindowInsets,
|
||||
applyImmediately: Boolean = true
|
||||
) {
|
||||
Mockito.clearInvocations(view)
|
||||
taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false)
|
||||
navigationModeCallback.onNavigationModeChanged(navigationMode)
|
||||
windowInsetsCallback.accept(insets)
|
||||
if (applyImmediately) {
|
||||
delayableExecutor.advanceClockToLast()
|
||||
delayableExecutor.runAllReady()
|
||||
}
|
||||
}
|
||||
|
||||
fun then(
|
||||
expectedContainerPadding: Int,
|
||||
expectedNotificationsMargin: Int = NOTIFICATIONS_MARGIN,
|
||||
expectedQsPadding: Int = 0
|
||||
) {
|
||||
verify(view).setPadding(anyInt(), anyInt(), anyInt(), eq(expectedContainerPadding))
|
||||
verify(view).setNotificationsMarginBottom(expectedNotificationsMargin)
|
||||
verify(view).setQSContainerPaddingBottom(expectedQsPadding)
|
||||
Mockito.clearInvocations(view)
|
||||
}
|
||||
|
||||
private fun windowInsets() = mock(WindowInsets::class.java, RETURNS_DEEP_STUBS)
|
||||
|
||||
private fun emptyInsets() = mock(WindowInsets::class.java)
|
||||
|
||||
private fun WindowInsets.withCutout(): WindowInsets {
|
||||
whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
|
||||
return this
|
||||
}
|
||||
|
||||
private fun WindowInsets.withStableBottom(): WindowInsets {
|
||||
whenever(stableInsetBottom).thenReturn(STABLE_INSET_BOTTOM)
|
||||
return this
|
||||
}
|
||||
|
||||
private fun getConstraintSetLayout(@IdRes id: Int): ConstraintSet.Layout {
|
||||
return constraintSetCaptor.value.getConstraint(id).layout
|
||||
}
|
||||
|
||||
private fun newViewWithId(id: Int): View {
|
||||
val view = View(mContext)
|
||||
view.id = id
|
||||
val layoutParams =
|
||||
ConstraintLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
// required as cloning ConstraintSet fails if view doesn't have layout params
|
||||
view.layoutParams = layoutParams
|
||||
return view
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val STABLE_INSET_BOTTOM = 100
|
||||
const val CUTOUT_HEIGHT = 50
|
||||
const val GESTURES_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
|
||||
const val BUTTONS_NAVIGATION = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
|
||||
const val NOTIFICATIONS_MARGIN = 50
|
||||
const val SCRIM_MARGIN = 10
|
||||
const val FOOTER_ACTIONS_INSET = 2
|
||||
const val FOOTER_ACTIONS_PADDING = 2
|
||||
const val FOOTER_ACTIONS_OFFSET = FOOTER_ACTIONS_INSET + FOOTER_ACTIONS_PADDING
|
||||
const val QS_PADDING_OFFSET = SCRIM_MARGIN + FOOTER_ACTIONS_OFFSET
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.statusbar.notification.stack.domain.interactor
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SharedNotificationContainerInteractorTest : SysuiTestCase() {
|
||||
private lateinit var configurationRepository: FakeConfigurationRepository
|
||||
private lateinit var underTest: SharedNotificationContainerInteractor
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
configurationRepository = FakeConfigurationRepository()
|
||||
underTest =
|
||||
SharedNotificationContainerInteractor(
|
||||
configurationRepository,
|
||||
mContext,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateConfigValues() = runTest {
|
||||
overrideResource(R.bool.config_use_split_notification_shade, true)
|
||||
overrideResource(R.bool.config_use_large_screen_shade_header, false)
|
||||
overrideResource(R.dimen.notification_panel_margin_horizontal, 0)
|
||||
overrideResource(R.dimen.notification_panel_margin_bottom, 10)
|
||||
overrideResource(R.dimen.notification_panel_margin_top, 10)
|
||||
overrideResource(R.dimen.large_screen_shade_header_height, 0)
|
||||
|
||||
val dimens = collectLastValue(underTest.configurationBasedDimensions)
|
||||
|
||||
configurationRepository.onAnyConfigurationChange()
|
||||
runCurrent()
|
||||
|
||||
val lastDimens = dimens()!!
|
||||
|
||||
assertThat(lastDimens.useSplitShade).isTrue()
|
||||
assertThat(lastDimens.useLargeScreenHeader).isFalse()
|
||||
assertThat(lastDimens.marginHorizontal).isEqualTo(0)
|
||||
assertThat(lastDimens.marginBottom).isGreaterThan(0)
|
||||
assertThat(lastDimens.marginTop).isGreaterThan(0)
|
||||
assertThat(lastDimens.marginTopLargeScreen).isEqualTo(0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.statusbar.notification.stack.ui.viewmodel
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SharedNotificationContainerViewModelTest : SysuiTestCase() {
|
||||
private lateinit var configurationRepository: FakeConfigurationRepository
|
||||
private lateinit var sharedNotificationContainerInteractor:
|
||||
SharedNotificationContainerInteractor
|
||||
private lateinit var underTest: SharedNotificationContainerViewModel
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
configurationRepository = FakeConfigurationRepository()
|
||||
sharedNotificationContainerInteractor =
|
||||
SharedNotificationContainerInteractor(
|
||||
configurationRepository,
|
||||
mContext,
|
||||
)
|
||||
underTest = SharedNotificationContainerViewModel(sharedNotificationContainerInteractor)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateMarginStartInSplitShade() = runTest {
|
||||
overrideResource(R.bool.config_use_split_notification_shade, true)
|
||||
overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
|
||||
|
||||
val dimens = collectLastValue(underTest.configurationBasedDimensions)
|
||||
|
||||
configurationRepository.onAnyConfigurationChange()
|
||||
runCurrent()
|
||||
|
||||
val lastDimens = dimens()!!
|
||||
|
||||
assertThat(lastDimens.marginStart).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateMarginStart() = runTest {
|
||||
overrideResource(R.bool.config_use_split_notification_shade, false)
|
||||
overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
|
||||
|
||||
val dimens = collectLastValue(underTest.configurationBasedDimensions)
|
||||
|
||||
configurationRepository.onAnyConfigurationChange()
|
||||
runCurrent()
|
||||
|
||||
val lastDimens = dimens()!!
|
||||
|
||||
assertThat(lastDimens.marginStart).isEqualTo(20)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateMarginEnd() = runTest {
|
||||
overrideResource(R.dimen.notification_panel_margin_horizontal, 50)
|
||||
|
||||
val dimens = collectLastValue(underTest.configurationBasedDimensions)
|
||||
|
||||
configurationRepository.onAnyConfigurationChange()
|
||||
runCurrent()
|
||||
|
||||
val lastDimens = dimens()!!
|
||||
|
||||
assertThat(lastDimens.marginEnd).isEqualTo(50)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateMarginBottom() = runTest {
|
||||
overrideResource(R.dimen.notification_panel_margin_bottom, 50)
|
||||
|
||||
val dimens = collectLastValue(underTest.configurationBasedDimensions)
|
||||
|
||||
configurationRepository.onAnyConfigurationChange()
|
||||
runCurrent()
|
||||
|
||||
val lastDimens = dimens()!!
|
||||
|
||||
assertThat(lastDimens.marginBottom).isEqualTo(50)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateMarginTopWithLargeScreenHeader() = runTest {
|
||||
overrideResource(R.bool.config_use_large_screen_shade_header, true)
|
||||
overrideResource(R.dimen.large_screen_shade_header_height, 50)
|
||||
overrideResource(R.dimen.notification_panel_margin_top, 0)
|
||||
|
||||
val dimens = collectLastValue(underTest.configurationBasedDimensions)
|
||||
|
||||
configurationRepository.onAnyConfigurationChange()
|
||||
runCurrent()
|
||||
|
||||
val lastDimens = dimens()!!
|
||||
|
||||
assertThat(lastDimens.marginTop).isEqualTo(50)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun validateMarginTop() = runTest {
|
||||
overrideResource(R.bool.config_use_large_screen_shade_header, false)
|
||||
overrideResource(R.dimen.large_screen_shade_header_height, 50)
|
||||
overrideResource(R.dimen.notification_panel_margin_top, 0)
|
||||
|
||||
val dimens = collectLastValue(underTest.configurationBasedDimensions)
|
||||
|
||||
configurationRepository.onAnyConfigurationChange()
|
||||
runCurrent()
|
||||
|
||||
val lastDimens = dimens()!!
|
||||
|
||||
assertThat(lastDimens.marginTop).isEqualTo(0)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user