diff options
21 files changed, 319 insertions, 2075 deletions
diff --git a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml deleted file mode 100644 index ee588f997ab8..000000000000 --- a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml +++ /dev/null @@ -1,105 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2022 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. ---> -<!-- TODO(b/242040009): Remove this file. --> -<FrameLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="0dp" - android:layout_height="@dimen/qs_security_footer_single_line_height" - android:layout_weight="1" - android:gravity="center" - android:clickable="true" - android:visibility="gone"> - - <LinearLayout - android:id="@+id/fgs_text_container" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_marginEnd="@dimen/qs_footer_action_inset" - android:background="@drawable/qs_security_footer_background" - android:layout_gravity="end" - android:gravity="center" - android:paddingHorizontal="@dimen/qs_footer_padding" - > - - <ImageView - android:id="@+id/primary_footer_icon" - android:layout_width="@dimen/qs_footer_icon_size" - android:layout_height="@dimen/qs_footer_icon_size" - android:gravity="start" - android:layout_marginEnd="12dp" - android:contentDescription="@null" - android:src="@drawable/ic_info_outline" - android:tint="?android:attr/textColorSecondary" /> - - <TextView - android:id="@+id/footer_text" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:maxLines="1" - android:ellipsize="end" - android:textAppearance="@style/TextAppearance.QS.SecurityFooter" - android:textColor="?android:attr/textColorSecondary"/> - - <ImageView - android:id="@+id/fgs_new" - android:layout_width="12dp" - android:layout_height="12dp" - android:scaleType="fitCenter" - android:src="@drawable/fgs_dot" - android:contentDescription="@string/fgs_dot_content_description" - /> - - <ImageView - android:id="@+id/footer_icon" - android:layout_width="@dimen/qs_footer_icon_size" - android:layout_height="@dimen/qs_footer_icon_size" - android:layout_marginStart="8dp" - android:contentDescription="@null" - android:src="@*android:drawable/ic_chevron_end" - android:autoMirrored="true" - android:tint="?android:attr/textColorSecondary" /> - </LinearLayout> - - <FrameLayout - android:id="@+id/fgs_number_container" - android:layout_width="@dimen/qs_footer_action_button_size" - android:layout_height="@dimen/qs_footer_action_button_size" - android:background="@drawable/qs_footer_action_circle" - android:focusable="true" - android:visibility="gone"> - - <TextView - android:id="@+id/fgs_number" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.QS.SecurityFooter" - android:layout_gravity="center" - android:textColor="?android:attr/textColorPrimary" - android:textSize="18sp"/> - <ImageView - android:id="@+id/fgs_collapsed_new" - android:layout_width="12dp" - android:layout_height="12dp" - android:scaleType="fitCenter" - android:layout_gravity="bottom|end" - android:src="@drawable/fgs_dot" - android:contentDescription="@string/fgs_dot_content_description" - /> - </FrameLayout> - -</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml index 2261ae8d7714..4a2a1cb9dc6d 100644 --- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml +++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml @@ -16,10 +16,8 @@ --> <!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc --> -<!-- TODO(b/242040009): Clean up this file. --> -<com.android.systemui.qs.FooterActionsView +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:layout_width="match_parent" android:layout_height="@dimen/footer_actions_height" android:elevation="@dimen/qs_panel_elevation" @@ -28,74 +26,4 @@ android:background="@drawable/qs_footer_actions_background" android:gravity="center_vertical|end" android:layout_gravity="bottom" -> - - <LinearLayout - android:id="@+id/security_footers_container" - android:orientation="horizontal" - android:layout_height="@dimen/qs_footer_action_button_size" - android:layout_width="0dp" - android:layout_weight="1" - /> - - <!-- Negative margin equal to --> - <LinearLayout - android:layout_height="match_parent" - android:layout_width="wrap_content" - android:layout_marginEnd="@dimen/qs_footer_action_inset_negative" - > - - <com.android.systemui.statusbar.phone.MultiUserSwitch - android:id="@id/multi_user_switch" - android:layout_width="@dimen/qs_footer_action_button_size" - android:layout_height="@dimen/qs_footer_action_button_size" - android:background="@drawable/qs_footer_action_circle" - android:focusable="true"> - - <ImageView - android:id="@+id/multi_user_avatar" - android:layout_width="@dimen/qs_footer_icon_size" - android:layout_height="@dimen/qs_footer_icon_size" - android:layout_gravity="center" - android:scaleType="centerInside" /> - </com.android.systemui.statusbar.phone.MultiUserSwitch> - - <com.android.systemui.statusbar.AlphaOptimizedFrameLayout - android:id="@id/settings_button_container" - android:layout_width="@dimen/qs_footer_action_button_size" - android:layout_height="@dimen/qs_footer_action_button_size" - android:background="@drawable/qs_footer_action_circle" - android:clipChildren="false" - android:clipToPadding="false"> - - <com.android.systemui.statusbar.phone.SettingsButton - android:id="@+id/settings_button" - android:layout_width="@dimen/qs_footer_icon_size" - android:layout_height="@dimen/qs_footer_icon_size" - android:layout_gravity="center" - android:background="@android:color/transparent" - android:focusable="false" - android:clickable="false" - android:importantForAccessibility="yes" - android:contentDescription="@string/accessibility_quick_settings_settings" - android:scaleType="centerInside" - android:src="@drawable/ic_settings" - android:tint="?android:attr/textColorPrimary" /> - - </com.android.systemui.statusbar.AlphaOptimizedFrameLayout> - - <com.android.systemui.statusbar.AlphaOptimizedImageView - android:id="@id/pm_lite" - android:layout_width="@dimen/qs_footer_action_button_size" - android:layout_height="@dimen/qs_footer_action_button_size" - android:background="@drawable/qs_footer_action_circle_color" - android:clickable="true" - android:clipToPadding="false" - android:focusable="true" - android:padding="@dimen/qs_footer_icon_padding" - android:src="@*android:drawable/ic_lock_power_off" - android:contentDescription="@string/accessibility_quick_settings_power_menu" - android:tint="?androidprv:attr/textColorOnAccent" /> - - </LinearLayout> -</com.android.systemui.qs.FooterActionsView>
\ No newline at end of file +/>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/quick_settings_security_footer.xml b/packages/SystemUI/res/layout/quick_settings_security_footer.xml deleted file mode 100644 index 194f3dd5dc26..000000000000 --- a/packages/SystemUI/res/layout/quick_settings_security_footer.xml +++ /dev/null @@ -1,61 +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. ---> -<!-- TODO(b/242040009): Remove this file. --> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="0dp" - android:layout_height="@dimen/qs_security_footer_single_line_height" - android:layout_weight="1" - android:clickable="true" - android:orientation="horizontal" - android:paddingHorizontal="@dimen/qs_footer_padding" - android:gravity="center_vertical" - android:layout_gravity="center_vertical|center_horizontal" - android:layout_marginEnd="@dimen/qs_footer_action_inset" - android:background="@drawable/qs_security_footer_background" - > - - <ImageView - android:id="@+id/primary_footer_icon" - android:layout_width="@dimen/qs_footer_icon_size" - android:layout_height="@dimen/qs_footer_icon_size" - android:gravity="start" - android:layout_marginEnd="12dp" - android:contentDescription="@null" - android:tint="?android:attr/textColorSecondary" /> - - <TextView - android:id="@+id/footer_text" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:singleLine="true" - android:ellipsize="end" - android:textAppearance="@style/TextAppearance.QS.SecurityFooter" - android:textColor="?android:attr/textColorSecondary"/> - - <ImageView - android:id="@+id/footer_icon" - android:layout_width="@dimen/qs_footer_icon_size" - android:layout_height="@dimen/qs_footer_icon_size" - android:layout_marginStart="8dp" - android:contentDescription="@null" - android:src="@*android:drawable/ic_chevron_end" - android:autoMirrored="true" - android:tint="?android:attr/textColorSecondary" /> - -</LinearLayout> diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index c322b129b908..2c3b3b3c7cde 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -204,9 +204,6 @@ object Flags { "full_screen_user_switcher" ) - // TODO(b/254512678): Tracking Bug - @JvmField val NEW_FOOTER_ACTIONS = releasedFlag(507, "new_footer_actions") - // TODO(b/244064524): Tracking Bug @JvmField val QS_SECONDARY_DATA_SUB_INFO = diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt index 3c1077867582..930de1302c58 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt @@ -122,10 +122,6 @@ interface FgsManagerController { /** Remove a [OnDialogDismissedListener]. */ fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener) - /** Whether we should update the footer visibility. */ - // TODO(b/242040009): Remove this. - fun shouldUpdateFooterVisibility(): Boolean - @VisibleForTesting fun visibleButtonsCount(): Int @@ -375,8 +371,6 @@ class FgsManagerControllerImpl @Inject constructor( } } - override fun shouldUpdateFooterVisibility() = dialog == null - override fun showDialog(expandable: Expandable?) { synchronized(lock) { if (dialog == null) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt index a9943e886339..b52233fc748b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 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. @@ -16,261 +16,17 @@ package com.android.systemui.qs -import android.content.Intent -import android.content.res.Configuration -import android.os.Handler -import android.os.UserManager -import android.provider.Settings -import android.provider.Settings.Global.USER_SWITCHER_ENABLED -import android.view.View -import android.view.ViewGroup -import androidx.annotation.VisibleForTesting -import com.android.internal.jank.InteractionJankMonitor -import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger -import com.android.internal.logging.nano.MetricsProto -import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.R -import com.android.systemui.animation.ActivityLaunchAnimator -import com.android.systemui.animation.Expandable -import com.android.systemui.globalactions.GlobalActionsDialogLite -import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.plugins.FalsingManager -import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED -import com.android.systemui.qs.dagger.QSScope -import com.android.systemui.settings.UserTracker -import com.android.systemui.statusbar.phone.MultiUserSwitchController -import com.android.systemui.statusbar.policy.ConfigurationController -import com.android.systemui.statusbar.policy.DeviceProvisionedController -import com.android.systemui.statusbar.policy.UserInfoController -import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener -import com.android.systemui.util.LargeScreenUtils -import com.android.systemui.util.ViewController -import com.android.systemui.util.settings.GlobalSettings +import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject -import javax.inject.Named -import javax.inject.Provider -/** - * Manages [FooterActionsView] behaviour, both when it's placed in QS or QQS (split shade). - * Main difference between QS and QQS behaviour is condition when buttons should be visible, - * determined by [buttonsVisibleState] - */ -@QSScope -// TODO(b/242040009): Remove this file. -internal class FooterActionsController @Inject constructor( - view: FooterActionsView, - multiUserSwitchControllerFactory: MultiUserSwitchController.Factory, - private val activityStarter: ActivityStarter, - private val userManager: UserManager, - private val userTracker: UserTracker, - private val userInfoController: UserInfoController, - private val deviceProvisionedController: DeviceProvisionedController, - private val securityFooterController: QSSecurityFooter, - private val fgsManagerFooterController: QSFgsManagerFooter, - private val falsingManager: FalsingManager, - private val metricsLogger: MetricsLogger, - private val globalActionsDialogProvider: Provider<GlobalActionsDialogLite>, - private val uiEventLogger: UiEventLogger, - @Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean, - private val globalSetting: GlobalSettings, - private val handler: Handler, - private val configurationController: ConfigurationController, -) : ViewController<FooterActionsView>(view) { - - private var globalActionsDialog: GlobalActionsDialogLite? = null - - private var lastExpansion = -1f - private var listening: Boolean = false - private var inSplitShade = false - - private val singleShadeAnimator by lazy { - // In single shade, the actions footer should only appear at the end of the expansion, - // so that it doesn't overlap with the notifications panel. - TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).setStartDelay(0.9f).build() - } - - private val splitShadeAnimator by lazy { - // The Actions footer view has its own background which is the same color as the qs panel's - // background. - // We don't want it to fade in at the same time as the rest of the panel, otherwise it is - // more opaque than the rest of the panel's background. Only applies to split shade. - val alphaAnimator = TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).build() - val bgAlphaAnimator = - TouchAnimator.Builder() - .addFloat(mView, "backgroundAlpha", 0f, 1f) - .setStartDelay(0.9f) - .build() - // In split shade, we want the actions footer to fade in exactly at the same time as the - // rest of the shade, as there is no overlap. - TouchAnimator.Builder() - .addFloat(alphaAnimator, "position", 0f, 1f) - .addFloat(bgAlphaAnimator, "position", 0f, 1f) - .build() - } - - private val animators: TouchAnimator - get() = if (inSplitShade) splitShadeAnimator else singleShadeAnimator - - var visible = true - set(value) { - field = value - updateVisibility() - } - - private val settingsButtonContainer: View = view.findViewById(R.id.settings_button_container) - private val securityFootersContainer: ViewGroup? = - view.findViewById(R.id.security_footers_container) - private val powerMenuLite: View = view.findViewById(R.id.pm_lite) - private val multiUserSwitchController = multiUserSwitchControllerFactory.create(view) - - @VisibleForTesting - internal val securityFootersSeparator = View(context).apply { visibility = View.GONE } - - private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ -> - val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser()) - mView.onUserInfoChanged(picture, isGuestUser) - } - - private val multiUserSetting = - object : SettingObserver( - globalSetting, handler, USER_SWITCHER_ENABLED, userTracker.userId) { - override fun handleValueChanged(value: Int, observedChange: Boolean) { - if (observedChange) { - updateView() - } - } - } - - private val onClickListener = View.OnClickListener { v -> - // Don't do anything if the tap looks suspicious. - if (!visible || falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - return@OnClickListener - } - if (v === settingsButtonContainer) { - if (!deviceProvisionedController.isCurrentUserSetup) { - // If user isn't setup just unlock the device and dump them back at SUW. - activityStarter.postQSRunnableDismissingKeyguard {} - return@OnClickListener - } - metricsLogger.action(MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH) - startSettingsActivity() - } else if (v === powerMenuLite) { - uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS) - globalActionsDialog?.showOrHideDialog(false, true, Expandable.fromView(powerMenuLite)) - } - } - - private val configurationListener = - object : ConfigurationController.ConfigurationListener { - override fun onConfigChanged(newConfig: Configuration?) { - updateResources() - } - } - - private fun updateResources() { - inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(resources) - } - - override fun onInit() { - multiUserSwitchController.init() - securityFooterController.init() - fgsManagerFooterController.init() - } - - private fun updateVisibility() { - val previousVisibility = mView.visibility - mView.visibility = if (visible) View.VISIBLE else View.INVISIBLE - if (previousVisibility != mView.visibility) updateView() - } - - private fun startSettingsActivity() { - val animationController = settingsButtonContainer?.let { - ActivityLaunchAnimator.Controller.fromView( - it, - InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON) - } - activityStarter.startActivity(Intent(Settings.ACTION_SETTINGS), - true /* dismissShade */, animationController) - } - - @VisibleForTesting - public override fun onViewAttached() { - globalActionsDialog = globalActionsDialogProvider.get() - if (showPMLiteButton) { - powerMenuLite.visibility = View.VISIBLE - powerMenuLite.setOnClickListener(onClickListener) - } else { - powerMenuLite.visibility = View.GONE - } - settingsButtonContainer.setOnClickListener(onClickListener) - multiUserSetting.isListening = true - - val securityFooter = securityFooterController.view - securityFootersContainer?.addView(securityFooter) - val separatorWidth = resources.getDimensionPixelSize(R.dimen.qs_footer_action_inset) - securityFootersContainer?.addView(securityFootersSeparator, separatorWidth, 1) - - val fgsFooter = fgsManagerFooterController.view - securityFootersContainer?.addView(fgsFooter) - - val visibilityListener = - VisibilityChangedDispatcher.OnVisibilityChangedListener { visibility -> - if (securityFooter.visibility == View.VISIBLE && - fgsFooter.visibility == View.VISIBLE) { - securityFootersSeparator.visibility = View.VISIBLE - } else { - securityFootersSeparator.visibility = View.GONE - } - fgsManagerFooterController - .setCollapsed(securityFooter.visibility == View.VISIBLE) - } - securityFooterController.setOnVisibilityChangedListener(visibilityListener) - fgsManagerFooterController.setOnVisibilityChangedListener(visibilityListener) - - configurationController.addCallback(configurationListener) - - updateResources() - updateView() - } - - private fun updateView() { - mView.updateEverything(multiUserSwitchController.isMultiUserEnabled) - } - - override fun onViewDetached() { - globalActionsDialog?.destroy() - globalActionsDialog = null - setListening(false) - multiUserSetting.isListening = false - configurationController.removeCallback(configurationListener) - } - - fun setListening(listening: Boolean) { - if (this.listening == listening) { - return - } - this.listening = listening - if (this.listening) { - userInfoController.addCallback(onUserInfoChangedListener) - updateView() - } else { - userInfoController.removeCallback(onUserInfoChangedListener) - } - - fgsManagerFooterController.setListening(listening) - securityFooterController.setListening(listening) - } - - fun disable(state2: Int) { - mView.disable(state2, multiUserSwitchController.isMultiUserEnabled) - } - - fun setExpansion(headerExpansionFraction: Float) { - animators.setPosition(headerExpansionFraction) - } - - fun setKeyguardShowing(showing: Boolean) { - setExpansion(lastExpansion) +/** Controller for the footer actions. This manages the initialization of its dependencies. */ +@SysUISingleton +class FooterActionsController +@Inject +constructor( + private val fgsManagerController: FgsManagerController, +) { + fun init() { + fgsManagerController.init() } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt deleted file mode 100644 index d602b0b27977..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2021 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.qs - -import android.app.StatusBarManager -import android.content.Context -import android.graphics.PorterDuff -import android.graphics.drawable.Drawable -import android.graphics.drawable.RippleDrawable -import android.os.UserManager -import android.util.AttributeSet -import android.util.Log -import android.view.MotionEvent -import android.view.View -import android.widget.ImageView -import android.widget.LinearLayout -import androidx.annotation.Keep -import com.android.settingslib.Utils -import com.android.settingslib.drawable.UserIconDrawable -import com.android.systemui.R -import com.android.systemui.statusbar.phone.MultiUserSwitch - -/** - * Quick Settings bottom buttons placed in footer (aka utility bar) - always visible in expanded QS, - * in split shade mode visible also in collapsed state. May contain up to 5 buttons: settings, - * edit tiles, power off and conditionally: user switch and tuner - */ -// TODO(b/242040009): Remove this file. -class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) { - private lateinit var settingsContainer: View - private lateinit var multiUserSwitch: MultiUserSwitch - private lateinit var multiUserAvatar: ImageView - - private var qsDisabled = false - private var expansionAmount = 0f - - /** - * Sets the alpha of the background of this view. - * - * Used from a [TouchAnimator] in the controller. - */ - var backgroundAlpha: Float = 1f - @Keep - set(value) { - field = value - background?.alpha = (value * 255).toInt() - } - @Keep get - - override fun onFinishInflate() { - super.onFinishInflate() - settingsContainer = findViewById(R.id.settings_button_container) - multiUserSwitch = findViewById(R.id.multi_user_switch) - multiUserAvatar = multiUserSwitch.findViewById(R.id.multi_user_avatar) - - // RenderThread is doing more harm than good when touching the header (to expand quick - // settings), so disable it for this view - if (settingsContainer.background is RippleDrawable) { - (settingsContainer.background as RippleDrawable).setForceSoftware(true) - } - importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES - } - - fun disable( - state2: Int, - multiUserEnabled: Boolean - ) { - val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0 - if (disabled == qsDisabled) return - qsDisabled = disabled - updateEverything(multiUserEnabled) - } - - fun updateEverything( - multiUserEnabled: Boolean - ) { - post { - updateVisibilities(multiUserEnabled) - updateClickabilities() - isClickable = false - } - } - - private fun updateClickabilities() { - multiUserSwitch.isClickable = multiUserSwitch.visibility == VISIBLE - settingsContainer.isClickable = settingsContainer.visibility == VISIBLE - } - - private fun updateVisibilities( - multiUserEnabled: Boolean - ) { - settingsContainer.visibility = if (qsDisabled) GONE else VISIBLE - multiUserSwitch.visibility = if (multiUserEnabled) VISIBLE else GONE - val isDemo = UserManager.isDeviceInDemoMode(context) - settingsContainer.visibility = if (isDemo) INVISIBLE else VISIBLE - } - - fun onUserInfoChanged(picture: Drawable?, isGuestUser: Boolean) { - var pictureToSet = picture - if (picture != null && isGuestUser && picture !is UserIconDrawable) { - pictureToSet = picture.constantState.newDrawable(resources).mutate() - pictureToSet.setColorFilter( - Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorForeground), - PorterDuff.Mode.SRC_IN) - } - multiUserAvatar.setImageDrawable(pictureToSet) - } - - override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { - if (VERBOSE) Log.d(TAG, "FooterActionsView onInterceptTouchEvent ${ev?.string}") - return super.onInterceptTouchEvent(ev) - } - - override fun onTouchEvent(event: MotionEvent?): Boolean { - if (VERBOSE) Log.d(TAG, "FooterActionsView onTouchEvent ${event?.string}") - return super.onTouchEvent(event) - } -} -private const val TAG = "FooterActionsView" -private val VERBOSE = Log.isLoggable(TAG, Log.VERBOSE) -private val MotionEvent.string - get() = "($id): ($x,$y)" diff --git a/packages/SystemUI/src/com/android/systemui/qs/NewFooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/NewFooterActionsController.kt deleted file mode 100644 index 7c67d9f42b55..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/NewFooterActionsController.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2022 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.qs - -import com.android.systemui.dagger.SysUISingleton -import javax.inject.Inject - -/** Controller for the footer actions. This manages the initialization of its dependencies. */ -@SysUISingleton -class NewFooterActionsController -@Inject -// TODO(b/242040009): Rename this to FooterActionsController. -constructor( - private val fgsManagerController: FgsManagerController, -) { - fun init() { - fgsManagerController.init() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index dc9dcc295e6e..0c242d9da25f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -215,7 +215,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { // Some views are always full width or have dependent padding continue; } - if (!(view instanceof FooterActionsView)) { + if (view.getId() != R.id.qs_footer_actions) { // Only padding for FooterActionsView, no margin. That way, the background goes // all the way to the edge. LayoutParams lp = (LayoutParams) view.getLayoutParams(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java deleted file mode 100644 index b1b9dd721eaf..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (C) 2022 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.qs; - -import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FGS_MANAGER_FOOTER_VIEW; -import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat; - -import android.content.Context; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.Nullable; - -import com.android.systemui.R; -import com.android.systemui.animation.Expandable; -import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.qs.dagger.QSScope; - -import java.util.concurrent.Executor; - -import javax.inject.Inject; -import javax.inject.Named; - -/** - * Footer entry point for the foreground service manager - */ -// TODO(b/242040009): Remove this file. -@QSScope -public class QSFgsManagerFooter implements View.OnClickListener, - FgsManagerController.OnDialogDismissedListener, - FgsManagerController.OnNumberOfPackagesChangedListener, - VisibilityChangedDispatcher { - - private final View mRootView; - private final TextView mFooterText; - private final Context mContext; - private final Executor mMainExecutor; - private final Executor mExecutor; - - private final FgsManagerController mFgsManagerController; - - private boolean mIsInitialized = false; - private int mNumPackages; - - private final View mTextContainer; - private final View mNumberContainer; - private final TextView mNumberView; - private final ImageView mDotView; - private final ImageView mCollapsedDotView; - - @Nullable - private VisibilityChangedDispatcher.OnVisibilityChangedListener mVisibilityChangedListener; - - @Inject - QSFgsManagerFooter(@Named(QS_FGS_MANAGER_FOOTER_VIEW) View rootView, - @Main Executor mainExecutor, @Background Executor executor, - FgsManagerController fgsManagerController) { - mRootView = rootView; - mFooterText = mRootView.findViewById(R.id.footer_text); - mTextContainer = mRootView.findViewById(R.id.fgs_text_container); - mNumberContainer = mRootView.findViewById(R.id.fgs_number_container); - mNumberView = mRootView.findViewById(R.id.fgs_number); - mDotView = mRootView.findViewById(R.id.fgs_new); - mCollapsedDotView = mRootView.findViewById(R.id.fgs_collapsed_new); - mContext = rootView.getContext(); - mMainExecutor = mainExecutor; - mExecutor = executor; - mFgsManagerController = fgsManagerController; - } - - /** - * Whether to show the footer in collapsed mode (just a number) or not (text). - * @param collapsed - */ - public void setCollapsed(boolean collapsed) { - mTextContainer.setVisibility(collapsed ? View.GONE : View.VISIBLE); - mNumberContainer.setVisibility(collapsed ? View.VISIBLE : View.GONE); - LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mRootView.getLayoutParams(); - lp.width = collapsed ? ViewGroup.LayoutParams.WRAP_CONTENT : 0; - lp.weight = collapsed ? 0f : 1f; - mRootView.setLayoutParams(lp); - } - - public void init() { - if (mIsInitialized) { - return; - } - - mFgsManagerController.init(); - - mRootView.setOnClickListener(this); - - mIsInitialized = true; - } - - public void setListening(boolean listening) { - if (listening) { - mFgsManagerController.addOnDialogDismissedListener(this); - mFgsManagerController.addOnNumberOfPackagesChangedListener(this); - mNumPackages = mFgsManagerController.getNumRunningPackages(); - refreshState(); - } else { - mFgsManagerController.removeOnDialogDismissedListener(this); - mFgsManagerController.removeOnNumberOfPackagesChangedListener(this); - } - } - - @Override - public void setOnVisibilityChangedListener( - @Nullable OnVisibilityChangedListener onVisibilityChangedListener) { - mVisibilityChangedListener = onVisibilityChangedListener; - } - - @Override - public void onClick(View view) { - mFgsManagerController.showDialog(Expandable.fromView(view)); - } - - public void refreshState() { - mExecutor.execute(this::handleRefreshState); - } - - public View getView() { - return mRootView; - } - - public void handleRefreshState() { - mMainExecutor.execute(() -> { - CharSequence text = icuMessageFormat(mContext.getResources(), - R.string.fgs_manager_footer_label, mNumPackages); - mFooterText.setText(text); - mNumberView.setText(Integer.toString(mNumPackages)); - mNumberView.setContentDescription(text); - if (mFgsManagerController.shouldUpdateFooterVisibility()) { - mRootView.setVisibility(mNumPackages > 0 - && mFgsManagerController.isAvailable().getValue() ? View.VISIBLE - : View.GONE); - int dotVis = mFgsManagerController.getShowFooterDot().getValue() - && mFgsManagerController.getNewChangesSinceDialogWasDismissed() - ? View.VISIBLE : View.GONE; - mDotView.setVisibility(dotVis); - mCollapsedDotView.setVisibility(dotVis); - if (mVisibilityChangedListener != null) { - mVisibilityChangedListener.onVisibilityChanged(mRootView.getVisibility()); - } - } - }); - } - - @Override - public void onDialogDismissed() { - refreshState(); - } - - @Override - public void onNumberOfPackagesChanged(int numPackages) { - mNumPackages = numPackages; - refreshState(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 71ab457659fe..f8fb4e89111e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -33,6 +33,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.widget.LinearLayout; import androidx.annotation.FloatRange; import androidx.annotation.Nullable; @@ -48,7 +49,6 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.media.controls.ui.MediaHost; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QS; @@ -114,7 +114,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private final QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger; private final QSTileHost mHost; private final FeatureFlags mFeatureFlags; - private final NewFooterActionsController mNewFooterActionsController; + private final FooterActionsController mFooterActionsController; private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory; private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner; private boolean mShowCollapsedOnKeyguard; @@ -132,9 +132,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private QSPanelController mQSPanelController; private QuickQSPanelController mQuickQSPanelController; private QSCustomizerController mQSCustomizerController; - @Nullable - private FooterActionsController mQSFooterActionController; - @Nullable private FooterActionsViewModel mQSFooterActionsViewModel; @Nullable private ScrollListener mScrollListener; @@ -185,7 +182,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca QSFragmentComponent.Factory qsComponentFactory, QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger, FalsingManager falsingManager, DumpManager dumpManager, FeatureFlags featureFlags, - NewFooterActionsController newFooterActionsController, + FooterActionsController footerActionsController, FooterActionsViewModel.Factory footerActionsViewModelFactory) { mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; mQsMediaHost = qsMediaHost; @@ -199,7 +196,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mStatusBarStateController = statusBarStateController; mDumpManager = dumpManager; mFeatureFlags = featureFlags; - mNewFooterActionsController = newFooterActionsController; + mFooterActionsController = footerActionsController; mFooterActionsViewModelFactory = footerActionsViewModelFactory; mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner(); } @@ -226,18 +223,12 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mQSPanelController.init(); mQuickQSPanelController.init(); - if (mFeatureFlags.isEnabled(Flags.NEW_FOOTER_ACTIONS)) { - mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */ - this); - FooterActionsView footerActionsView = view.findViewById(R.id.qs_footer_actions); - FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel, - mListeningAndVisibilityLifecycleOwner); - - mNewFooterActionsController.init(); - } else { - mQSFooterActionController = qsFragmentComponent.getQSFooterActionController(); - mQSFooterActionController.init(); - } + mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */ + this); + LinearLayout footerActionsView = view.findViewById(R.id.qs_footer_actions); + FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel, + mListeningAndVisibilityLifecycleOwner); + mFooterActionsController.init(); mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view); mQSPanelScrollView.addOnLayoutChangeListener( @@ -436,9 +427,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mContainer.disable(state1, state2, animate); mHeader.disable(state1, state2, animate); mFooter.disable(state1, state2, animate); - if (mQSFooterActionController != null) { - mQSFooterActionController.disable(state2); - } updateQsState(); } @@ -457,11 +445,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca boolean footerVisible = qsPanelVisible && (mQsExpanded || !keyguardShowing || mHeaderAnimating || mShowCollapsedOnKeyguard); mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE); - if (mQSFooterActionController != null) { - mQSFooterActionController.setVisible(footerVisible); - } else { - mQSFooterActionsViewModel.onVisibilityChangeRequested(footerVisible); - } + mQSFooterActionsViewModel.onVisibilityChangeRequested(footerVisible); mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard) || (mQsExpanded && !mStackScrollerOverscrolling)); mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE); @@ -534,9 +518,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca } mFooter.setKeyguardShowing(keyguardShowing); - if (mQSFooterActionController != null) { - mQSFooterActionController.setKeyguardShowing(keyguardShowing); - } updateQsState(); } @@ -552,9 +533,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca if (DEBUG) Log.d(TAG, "setListening " + listening); mListening = listening; mQSContainerImplController.setListening(listening && mQsVisible); - if (mQSFooterActionController != null) { - mQSFooterActionController.setListening(listening && mQsVisible); - } mListeningAndVisibilityLifecycleOwner.updateState(); updateQsPanelControllerListening(); } @@ -665,12 +643,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion); float footerActionsExpansion = onKeyguardAndExpanded ? 1 : mInSplitShade ? alphaProgress : expansion; - if (mQSFooterActionController != null) { - mQSFooterActionController.setExpansion(footerActionsExpansion); - } else { - mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion, - mInSplitShade); - } + mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion, + mInSplitShade); mQSPanelController.setRevealExpansion(expansion); mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation); mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation); @@ -835,11 +809,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca boolean customizing = isCustomizing(); mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); - if (mQSFooterActionController != null) { - mQSFooterActionController.setVisible(!customizing); - } else { - mQSFooterActionsViewModel.onVisibilityChangeRequested(!customizing); - } + mQSFooterActionsViewModel.onVisibilityChangeRequested(!customizing); mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); // Let the panel know the position changed and it needs to update where notifications // and whatnot are. @@ -927,6 +897,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca updateShowCollapsedOnKeyguard(); } + @VisibleForTesting + public ListeningAndVisibilityLifecycleOwner getListeningAndVisibilityLifecycleOwner() { + return mListeningAndVisibilityLifecycleOwner; + } + @Override public void dump(PrintWriter pw, String[] args) { IndentingPrintWriter indentingPw = new IndentingPrintWriter(pw, /* singleIndent= */ " "); @@ -994,7 +969,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca * - STARTED when mListening == true && mQsVisible == false. * - RESUMED when mListening == true && mQsVisible == true. */ - private class ListeningAndVisibilityLifecycleOwner implements LifecycleOwner { + @VisibleForTesting + class ListeningAndVisibilityLifecycleOwner implements LifecycleOwner { private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this); private boolean mDestroyed = false; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java deleted file mode 100644 index 6c1e95645550..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * 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. - */ -package com.android.systemui.qs; - -import static com.android.systemui.qs.dagger.QSFragmentModule.QS_SECURITY_FOOTER_VIEW; - -import android.app.admin.DevicePolicyEventLogger; -import android.app.admin.DevicePolicyManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Resources; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.UserHandle; -import android.util.Log; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.Nullable; - -import com.android.internal.util.FrameworkStatsLog; -import com.android.systemui.FontSizeUtils; -import com.android.systemui.R; -import com.android.systemui.animation.Expandable; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.common.shared.model.Icon; -import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.qs.dagger.QSScope; -import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig; -import com.android.systemui.security.data.model.SecurityModel; -import com.android.systemui.statusbar.policy.SecurityController; -import com.android.systemui.util.ViewController; - -import javax.inject.Inject; -import javax.inject.Named; - -/** ViewController for the footer actions. */ -// TODO(b/242040009): Remove this class. -@QSScope -public class QSSecurityFooter extends ViewController<View> - implements OnClickListener, VisibilityChangedDispatcher { - protected static final String TAG = "QSSecurityFooter"; - - private final TextView mFooterText; - private final ImageView mPrimaryFooterIcon; - private Context mContext; - private final Callback mCallback = new Callback(); - private final SecurityController mSecurityController; - private final Handler mMainHandler; - private final BroadcastDispatcher mBroadcastDispatcher; - private final QSSecurityFooterUtils mQSSecurityFooterUtils; - - protected H mHandler; - - private boolean mIsVisible; - private boolean mIsClickable; - @Nullable - private CharSequence mFooterTextContent = null; - private Icon mFooterIcon; - - @Nullable - private VisibilityChangedDispatcher.OnVisibilityChangedListener mVisibilityChangedListener; - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals( - DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG)) { - showDeviceMonitoringDialog(); - } - } - }; - - @Inject - QSSecurityFooter(@Named(QS_SECURITY_FOOTER_VIEW) View rootView, - @Main Handler mainHandler, SecurityController securityController, - @Background Looper bgLooper, BroadcastDispatcher broadcastDispatcher, - QSSecurityFooterUtils qSSecurityFooterUtils) { - super(rootView); - mFooterText = mView.findViewById(R.id.footer_text); - mPrimaryFooterIcon = mView.findViewById(R.id.primary_footer_icon); - mFooterIcon = new Icon.Resource( - R.drawable.ic_info_outline, /* contentDescription= */ null); - mContext = rootView.getContext(); - mSecurityController = securityController; - mMainHandler = mainHandler; - mHandler = new H(bgLooper); - mBroadcastDispatcher = broadcastDispatcher; - mQSSecurityFooterUtils = qSSecurityFooterUtils; - } - - @Override - protected void onViewAttached() { - // Use background handler, as it's the same thread that handleClick is called on. - mBroadcastDispatcher.registerReceiverWithHandler(mReceiver, - new IntentFilter(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG), - mHandler, UserHandle.ALL); - mView.setOnClickListener(this); - } - - @Override - protected void onViewDetached() { - mBroadcastDispatcher.unregisterReceiver(mReceiver); - mView.setOnClickListener(null); - } - - public void setListening(boolean listening) { - if (listening) { - mSecurityController.addCallback(mCallback); - refreshState(); - } else { - mSecurityController.removeCallback(mCallback); - } - } - - @Override - public void setOnVisibilityChangedListener( - @Nullable OnVisibilityChangedListener onVisibilityChangedListener) { - mVisibilityChangedListener = onVisibilityChangedListener; - } - - public void onConfigurationChanged() { - FontSizeUtils.updateFontSize(mFooterText, R.dimen.qs_tile_text_size); - Resources r = mContext.getResources(); - - int padding = r.getDimensionPixelSize(R.dimen.qs_footer_padding); - mView.setPaddingRelative(padding, 0, padding, 0); - mView.setBackground(mContext.getDrawable(R.drawable.qs_security_footer_background)); - } - - public View getView() { - return mView; - } - - public boolean hasFooter() { - return mView.getVisibility() != View.GONE; - } - - @Override - public void onClick(View v) { - if (!hasFooter()) return; - mHandler.sendEmptyMessage(H.CLICK); - } - - private void handleClick() { - showDeviceMonitoringDialog(); - DevicePolicyEventLogger - .createEvent(FrameworkStatsLog.DEVICE_POLICY_EVENT__EVENT_ID__DO_USER_INFO_CLICKED) - .write(); - } - - // TODO(b/242040009): Remove this. - public void showDeviceMonitoringDialog() { - mQSSecurityFooterUtils.showDeviceMonitoringDialog(mContext, Expandable.fromView(mView)); - } - - public void refreshState() { - mHandler.sendEmptyMessage(H.REFRESH_STATE); - } - - private void handleRefreshState() { - SecurityModel securityModel = SecurityModel.create(mSecurityController); - SecurityButtonConfig buttonConfig = mQSSecurityFooterUtils.getButtonConfig(securityModel); - - if (buttonConfig == null) { - mIsVisible = false; - } else { - mIsVisible = true; - mIsClickable = buttonConfig.isClickable(); - mFooterTextContent = buttonConfig.getText(); - mFooterIcon = buttonConfig.getIcon(); - } - - // Update the UI. - mMainHandler.post(mUpdatePrimaryIcon); - mMainHandler.post(mUpdateDisplayState); - } - - private final Runnable mUpdatePrimaryIcon = new Runnable() { - @Override - public void run() { - if (mFooterIcon instanceof Icon.Loaded) { - mPrimaryFooterIcon.setImageDrawable(((Icon.Loaded) mFooterIcon).getDrawable()); - } else if (mFooterIcon instanceof Icon.Resource) { - mPrimaryFooterIcon.setImageResource(((Icon.Resource) mFooterIcon).getRes()); - } - } - }; - - private final Runnable mUpdateDisplayState = new Runnable() { - @Override - public void run() { - if (mFooterTextContent != null) { - mFooterText.setText(mFooterTextContent); - } - mView.setVisibility(mIsVisible ? View.VISIBLE : View.GONE); - if (mVisibilityChangedListener != null) { - mVisibilityChangedListener.onVisibilityChanged(mView.getVisibility()); - } - - if (mIsVisible && mIsClickable) { - mView.setClickable(true); - mView.findViewById(R.id.footer_icon).setVisibility(View.VISIBLE); - } else { - mView.setClickable(false); - mView.findViewById(R.id.footer_icon).setVisibility(View.GONE); - } - } - }; - - private class Callback implements SecurityController.SecurityControllerCallback { - @Override - public void onStateChanged() { - refreshState(); - } - } - - private class H extends Handler { - private static final int CLICK = 0; - private static final int REFRESH_STATE = 1; - - private H(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - String name = null; - try { - if (msg.what == REFRESH_STATE) { - name = "handleRefreshState"; - handleRefreshState(); - } else if (msg.what == CLICK) { - name = "handleClick"; - handleClick(); - } - } catch (Throwable t) { - final String error = "Error in " + name; - Log.w(TAG, error, t); - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java index aa505fb0b6bd..01eb636f75d6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java @@ -28,7 +28,6 @@ import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.dagger.qualifiers.RootView; import com.android.systemui.plugins.qs.QS; import com.android.systemui.privacy.OngoingPrivacyChip; -import com.android.systemui.qs.FooterActionsView; import com.android.systemui.qs.QSContainerImpl; import com.android.systemui.qs.QSFooter; import com.android.systemui.qs.QSFooterView; @@ -51,8 +50,6 @@ import dagger.Provides; */ @Module public interface QSFragmentModule { - String QS_FGS_MANAGER_FOOTER_VIEW = "qs_fgs_manager_footer"; - String QS_SECURITY_FOOTER_VIEW = "qs_security_footer"; String QS_USING_MEDIA_PLAYER = "qs_using_media_player"; String QS_USING_COLLAPSED_LANDSCAPE_MEDIA = "qs_using_collapsed_landscape_media"; @@ -119,16 +116,6 @@ public interface QSFragmentModule { return view.findViewById(R.id.qs_footer); } - /** - * Provides a {@link FooterActionsView}. - * - * This will replace a ViewStub either in {@link QSFooterView} or in {@link QSContainerImpl}. - */ - @Provides - static FooterActionsView providesQSFooterActionsView(@RootView View view) { - return view.findViewById(R.id.qs_footer_actions); - } - /** */ @Provides @QSScope @@ -146,18 +133,6 @@ public interface QSFragmentModule { /** */ @Provides - @QSScope - @Named(QS_SECURITY_FOOTER_VIEW) - static View providesQSSecurityFooterView( - @QSThemedContext LayoutInflater layoutInflater, - FooterActionsView footerActionsView - ) { - return layoutInflater.inflate(R.layout.quick_settings_security_footer, footerActionsView, - false); - } - - /** */ - @Provides @Named(QS_USING_MEDIA_PLAYER) static boolean providesQSUsingMediaPlayer(Context context) { return useQsMediaPlayer(context); @@ -183,15 +158,4 @@ public interface QSFragmentModule { static StatusIconContainer providesStatusIconContainer(QuickStatusBarHeader qsHeader) { return qsHeader.findViewById(R.id.statusIcons); } - - /** */ - @Provides - @QSScope - @Named(QS_FGS_MANAGER_FOOTER_VIEW) - static View providesQSFgsManagerFooterView( - @QSThemedContext LayoutInflater layoutInflater, - FooterActionsView footerActionsView - ) { - return layoutInflater.inflate(R.layout.fgs_footer, footerActionsView, false); - } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt index 3e39c8ee62f1..6db3c9941b78 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt @@ -35,35 +35,31 @@ import com.android.systemui.animation.Expandable import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.people.ui.view.PeopleViewBinder.bind -import com.android.systemui.qs.FooterActionsView import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel +import kotlin.math.roundToInt import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch /** A ViewBinder for [FooterActionsViewBinder]. */ object FooterActionsViewBinder { - /** - * Create a [FooterActionsView] that can later be [bound][bind] to a [FooterActionsViewModel]. - */ + /** Create a view that can later be [bound][bind] to a [FooterActionsViewModel]. */ @JvmStatic - fun create(context: Context): FooterActionsView { + fun create(context: Context): LinearLayout { return LayoutInflater.from(context).inflate(R.layout.footer_actions, /* root= */ null) - as FooterActionsView + as LinearLayout } /** Bind [view] to [viewModel]. */ @JvmStatic fun bind( - view: FooterActionsView, + view: LinearLayout, viewModel: FooterActionsViewModel, qsVisibilityLifecycleOwner: LifecycleOwner, ) { - // Remove all children of the FooterActionsView that are used by the old implementation. - // TODO(b/242040009): Clean up the XML once the old implementation is removed. - view.removeAllViews() + view.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES // Add the views used by this new implementation. val context = view.context @@ -117,7 +113,11 @@ object FooterActionsViewBinder { } launch { viewModel.alpha.collect { view.alpha = it } } - launch { viewModel.backgroundAlpha.collect { view.backgroundAlpha = it } } + launch { + viewModel.backgroundAlpha.collect { + view.background?.alpha = (it * 255).roundToInt() + } + } } // Listen for model changes only when QS are visible. diff --git a/packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt b/packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt index 50af260684f8..1cf5a8fe5f97 100644 --- a/packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt +++ b/packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt @@ -17,6 +17,7 @@ package com.android.systemui.security.data.model import android.graphics.drawable.Drawable +import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.policy.SecurityController import kotlinx.coroutines.CoroutineDispatcher @@ -55,8 +56,8 @@ data class SecurityModel( * Important: This method should be called from a background thread as this will do a lot of * binder calls. */ - // TODO(b/242040009): Remove this. @JvmStatic + @VisibleForTesting fun create(securityController: SecurityController): SecurityModel { val deviceAdminInfo = if (securityController.isParentalControlsEnabled) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java deleted file mode 100644 index 5e2a7c8ca540..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2021 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.phone; - -import static com.android.systemui.DejankUtils.whitelistIpcs; - -import android.content.Intent; -import android.os.UserHandle; -import android.os.UserManager; -import android.view.View; -import android.view.ViewGroup; - -import com.android.systemui.R; -import com.android.systemui.animation.ActivityLaunchAnimator; -import com.android.systemui.animation.Expandable; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.qs.FooterActionsView; -import com.android.systemui.qs.dagger.QSScope; -import com.android.systemui.qs.user.UserSwitchDialogController; -import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter; -import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.user.UserSwitcherActivity; -import com.android.systemui.util.ViewController; - -import javax.inject.Inject; - -/** View Controller for {@link MultiUserSwitch}. */ -// TODO(b/242040009): Remove this file. -public class MultiUserSwitchController extends ViewController<MultiUserSwitch> { - private final UserManager mUserManager; - private final UserSwitcherController mUserSwitcherController; - private final FalsingManager mFalsingManager; - private final UserSwitchDialogController mUserSwitchDialogController; - private final ActivityStarter mActivityStarter; - private final FeatureFlags mFeatureFlags; - - private BaseUserSwitcherAdapter mUserListener; - - private final View.OnClickListener mOnClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - return; - } - - if (mFeatureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) { - Intent intent = new Intent(v.getContext(), UserSwitcherActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - - mActivityStarter.startActivity(intent, true /* dismissShade */, - ActivityLaunchAnimator.Controller.fromView(v, null), - true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM); - } else { - mUserSwitchDialogController.showDialog(v.getContext(), Expandable.fromView(v)); - } - } - }; - - @QSScope - public static class Factory { - private final UserManager mUserManager; - private final UserSwitcherController mUserSwitcherController; - private final FalsingManager mFalsingManager; - private final UserSwitchDialogController mUserSwitchDialogController; - private final ActivityStarter mActivityStarter; - private final FeatureFlags mFeatureFlags; - - @Inject - public Factory(UserManager userManager, UserSwitcherController userSwitcherController, - FalsingManager falsingManager, - UserSwitchDialogController userSwitchDialogController, FeatureFlags featureFlags, - ActivityStarter activityStarter) { - mUserManager = userManager; - mUserSwitcherController = userSwitcherController; - mFalsingManager = falsingManager; - mUserSwitchDialogController = userSwitchDialogController; - mActivityStarter = activityStarter; - mFeatureFlags = featureFlags; - } - - public MultiUserSwitchController create(FooterActionsView view) { - return new MultiUserSwitchController(view.findViewById(R.id.multi_user_switch), - mUserManager, mUserSwitcherController, - mFalsingManager, mUserSwitchDialogController, mFeatureFlags, - mActivityStarter); - } - } - - private MultiUserSwitchController(MultiUserSwitch view, UserManager userManager, - UserSwitcherController userSwitcherController, - FalsingManager falsingManager, UserSwitchDialogController userSwitchDialogController, - FeatureFlags featureFlags, ActivityStarter activityStarter) { - super(view); - mUserManager = userManager; - mUserSwitcherController = userSwitcherController; - mFalsingManager = falsingManager; - mUserSwitchDialogController = userSwitchDialogController; - mFeatureFlags = featureFlags; - mActivityStarter = activityStarter; - } - - @Override - protected void onInit() { - registerListener(); - mView.refreshContentDescription(getCurrentUser()); - } - - @Override - protected void onViewAttached() { - mView.setOnClickListener(mOnClickListener); - } - - @Override - protected void onViewDetached() { - mView.setOnClickListener(null); - } - - private void registerListener() { - if (mUserManager.isUserSwitcherEnabled() && mUserListener == null) { - - final UserSwitcherController controller = mUserSwitcherController; - if (controller != null) { - mUserListener = new BaseUserSwitcherAdapter(controller) { - @Override - public void notifyDataSetChanged() { - mView.refreshContentDescription(getCurrentUser()); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - return null; - } - }; - mView.refreshContentDescription(getCurrentUser()); - } - } - } - - private String getCurrentUser() { - // TODO(b/138661450) - if (whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled())) { - return mUserSwitcherController.getCurrentUserName(); - } - - return null; - } - - /** Returns true if view should be made visible. */ - public boolean isMultiUserEnabled() { - // TODO(b/138661450) Move IPC calls to background - return whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled( - getResources().getBoolean(R.bool.qs_show_user_switcher_for_single_user))); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt deleted file mode 100644 index 2ba8782c6d02..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt +++ /dev/null @@ -1,440 +0,0 @@ -package com.android.systemui.qs - -import android.content.Intent -import android.os.Handler -import android.os.UserManager -import android.provider.Settings -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import android.testing.ViewUtils -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.test.filters.SmallTest -import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger -import com.android.internal.logging.testing.FakeMetricsLogger -import com.android.systemui.R -import com.android.systemui.animation.ActivityLaunchAnimator -import com.android.systemui.classifier.FalsingManagerFake -import com.android.systemui.globalactions.GlobalActionsDialogLite -import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.settings.UserTracker -import com.android.systemui.statusbar.phone.MultiUserSwitchController -import com.android.systemui.statusbar.policy.DeviceProvisionedController -import com.android.systemui.statusbar.policy.FakeConfigurationController -import com.android.systemui.statusbar.policy.UserInfoController -import com.android.systemui.util.mockito.capture -import com.android.systemui.util.settings.FakeSettings -import com.android.systemui.utils.leaks.LeakCheckedTest -import com.google.common.truth.Expect -import com.google.common.truth.Truth.assertThat -import javax.inject.Provider -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.Captor -import org.mockito.Mock -import org.mockito.Mockito -import org.mockito.Mockito.atLeastOnce -import org.mockito.Mockito.clearInvocations -import org.mockito.Mockito.never -import org.mockito.Mockito.reset -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever -import org.mockito.MockitoAnnotations - -@SmallTest -@TestableLooper.RunWithLooper(setAsMainLooper = true) -@RunWith(AndroidTestingRunner::class) -class FooterActionsControllerTest : LeakCheckedTest() { - - @get:Rule var expect: Expect = Expect.create() - - @Mock private lateinit var userManager: UserManager - @Mock private lateinit var userTracker: UserTracker - @Mock private lateinit var activityStarter: ActivityStarter - @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController - @Mock private lateinit var userInfoController: UserInfoController - @Mock private lateinit var multiUserSwitchControllerFactory: MultiUserSwitchController.Factory - @Mock private lateinit var multiUserSwitchController: MultiUserSwitchController - @Mock private lateinit var globalActionsDialogProvider: Provider<GlobalActionsDialogLite> - @Mock private lateinit var globalActionsDialog: GlobalActionsDialogLite - @Mock private lateinit var uiEventLogger: UiEventLogger - @Mock private lateinit var securityFooterController: QSSecurityFooter - @Mock private lateinit var fgsManagerController: QSFgsManagerFooter - @Captor - private lateinit var visibilityChangedCaptor: - ArgumentCaptor<VisibilityChangedDispatcher.OnVisibilityChangedListener> - - private lateinit var controller: FooterActionsController - - private val configurationController = FakeConfigurationController() - private val metricsLogger: MetricsLogger = FakeMetricsLogger() - private val falsingManager: FalsingManagerFake = FalsingManagerFake() - private lateinit var view: FooterActionsView - private lateinit var testableLooper: TestableLooper - private lateinit var fakeSettings: FakeSettings - private lateinit var securityFooter: View - private lateinit var fgsFooter: View - - @Before - fun setUp() { - // We want to make sure testable resources are always used - context.ensureTestableResources() - - MockitoAnnotations.initMocks(this) - testableLooper = TestableLooper.get(this) - fakeSettings = FakeSettings() - - whenever(multiUserSwitchControllerFactory.create(any())) - .thenReturn(multiUserSwitchController) - whenever(globalActionsDialogProvider.get()).thenReturn(globalActionsDialog) - - securityFooter = View(mContext) - fgsFooter = View(mContext) - - whenever(securityFooterController.view).thenReturn(securityFooter) - whenever(fgsManagerController.view).thenReturn(fgsFooter) - - view = inflateView() - - controller = constructFooterActionsController(view) - controller.init() - ViewUtils.attachView(view) - // View looper is the testable looper associated with the test - testableLooper.processAllMessages() - } - - @After - fun tearDown() { - if (view.isAttachedToWindow) { - ViewUtils.detachView(view) - } - } - - @Test - fun testInitializesControllers() { - verify(multiUserSwitchController).init() - verify(fgsManagerController).init() - verify(securityFooterController).init() - } - - @Test - fun testLogPowerMenuClick() { - controller.visible = true - falsingManager.setFalseTap(false) - - view.findViewById<View>(R.id.pm_lite).performClick() - // Verify clicks are logged - verify(uiEventLogger, Mockito.times(1)) - .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS) - } - - @Test - fun testSettings() { - val captor = ArgumentCaptor.forClass(Intent::class.java) - whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true) - view.findViewById<View>(R.id.settings_button_container).performClick() - - verify(activityStarter) - .startActivity(capture(captor), anyBoolean(), any<ActivityLaunchAnimator.Controller>()) - - assertThat(captor.value.action).isEqualTo(Settings.ACTION_SETTINGS) - } - - @Test - fun testSettings_UserNotSetup() { - whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(false) - view.findViewById<View>(R.id.settings_button_container).performClick() - // Verify Settings wasn't launched. - verify(activityStarter, never()) - .startActivity(any(), anyBoolean(), any<ActivityLaunchAnimator.Controller>()) - } - - @Test - fun testMultiUserSwitchUpdatedWhenExpansionStarts() { - // When expansion starts, listening is set to true - val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch) - - assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE) - - whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true) - - controller.setListening(true) - testableLooper.processAllMessages() - - assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE) - } - - @Test - fun testMultiUserSwitchUpdatedWhenSettingChanged() { - // Always listening to setting while View is attached - testableLooper.processAllMessages() - - val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch) - assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE) - - // The setting is only used as an indicator for whether the view should refresh. The actual - // value of the setting is ignored; isMultiUserEnabled is the source of truth - whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true) - - // Changing the value of USER_SWITCHER_ENABLED should cause the view to update - fakeSettings.putIntForUser(Settings.Global.USER_SWITCHER_ENABLED, 1, userTracker.userId) - testableLooper.processAllMessages() - - assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE) - } - - @Test - fun testMultiUserSettingNotListenedAfterDetach() { - testableLooper.processAllMessages() - - val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch) - assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE) - - ViewUtils.detachView(view) - - // The setting is only used as an indicator for whether the view should refresh. The actual - // value of the setting is ignored; isMultiUserEnabled is the source of truth - whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true) - - // Changing the value of USER_SWITCHER_ENABLED should cause the view to update - fakeSettings.putIntForUser(Settings.Global.USER_SWITCHER_ENABLED, 1, userTracker.userId) - testableLooper.processAllMessages() - - assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE) - } - - @Test - fun testCleanUpGAD() { - reset(globalActionsDialogProvider) - // We are creating a new controller, so detach the views from it - (securityFooter.parent as ViewGroup).removeView(securityFooter) - (fgsFooter.parent as ViewGroup).removeView(fgsFooter) - - whenever(globalActionsDialogProvider.get()).thenReturn(globalActionsDialog) - val view = inflateView() - controller = constructFooterActionsController(view) - controller.init() - verify(globalActionsDialogProvider, never()).get() - - // GAD is constructed during attachment - ViewUtils.attachView(view) - testableLooper.processAllMessages() - verify(globalActionsDialogProvider).get() - - ViewUtils.detachView(view) - testableLooper.processAllMessages() - verify(globalActionsDialog).destroy() - } - - @Test - fun testSeparatorVisibility_noneVisible_gone() { - verify(securityFooterController) - .setOnVisibilityChangedListener(capture(visibilityChangedCaptor)) - val listener = visibilityChangedCaptor.value - val separator = controller.securityFootersSeparator - - setVisibilities(securityFooterVisible = false, fgsFooterVisible = false, listener) - assertThat(separator.visibility).isEqualTo(View.GONE) - } - - @Test - fun testSeparatorVisibility_onlySecurityFooterVisible_gone() { - verify(securityFooterController) - .setOnVisibilityChangedListener(capture(visibilityChangedCaptor)) - val listener = visibilityChangedCaptor.value - val separator = controller.securityFootersSeparator - - setVisibilities(securityFooterVisible = true, fgsFooterVisible = false, listener) - assertThat(separator.visibility).isEqualTo(View.GONE) - } - - @Test - fun testSeparatorVisibility_onlyFgsFooterVisible_gone() { - verify(securityFooterController) - .setOnVisibilityChangedListener(capture(visibilityChangedCaptor)) - val listener = visibilityChangedCaptor.value - val separator = controller.securityFootersSeparator - - setVisibilities(securityFooterVisible = false, fgsFooterVisible = true, listener) - assertThat(separator.visibility).isEqualTo(View.GONE) - } - - @Test - fun testSeparatorVisibility_bothVisible_visible() { - verify(securityFooterController) - .setOnVisibilityChangedListener(capture(visibilityChangedCaptor)) - val listener = visibilityChangedCaptor.value - val separator = controller.securityFootersSeparator - - setVisibilities(securityFooterVisible = true, fgsFooterVisible = true, listener) - assertThat(separator.visibility).isEqualTo(View.VISIBLE) - } - - @Test - fun testFgsFooterCollapsed() { - verify(securityFooterController) - .setOnVisibilityChangedListener(capture(visibilityChangedCaptor)) - val listener = visibilityChangedCaptor.value - - val booleanCaptor = ArgumentCaptor.forClass(Boolean::class.java) - - clearInvocations(fgsManagerController) - setVisibilities(securityFooterVisible = false, fgsFooterVisible = true, listener) - verify(fgsManagerController, atLeastOnce()).setCollapsed(capture(booleanCaptor)) - assertThat(booleanCaptor.allValues.last()).isFalse() - - clearInvocations(fgsManagerController) - setVisibilities(securityFooterVisible = true, fgsFooterVisible = true, listener) - verify(fgsManagerController, atLeastOnce()).setCollapsed(capture(booleanCaptor)) - assertThat(booleanCaptor.allValues.last()).isTrue() - } - - @Test - fun setExpansion_inSplitShade_alphaFollowsExpansion() { - enableSplitShade() - - controller.setExpansion(0f) - expect.that(view.alpha).isEqualTo(0f) - - controller.setExpansion(0.25f) - expect.that(view.alpha).isEqualTo(0.25f) - - controller.setExpansion(0.5f) - expect.that(view.alpha).isEqualTo(0.5f) - - controller.setExpansion(0.75f) - expect.that(view.alpha).isEqualTo(0.75f) - - controller.setExpansion(1f) - expect.that(view.alpha).isEqualTo(1f) - } - - @Test - fun setExpansion_inSplitShade_backgroundAlphaFollowsExpansion_with_0_9_delay() { - enableSplitShade() - - controller.setExpansion(0f) - expect.that(view.backgroundAlphaFraction).isEqualTo(0f) - - controller.setExpansion(0.5f) - expect.that(view.backgroundAlphaFraction).isEqualTo(0f) - - controller.setExpansion(0.9f) - expect.that(view.backgroundAlphaFraction).isEqualTo(0f) - - controller.setExpansion(0.91f) - expect.that(view.backgroundAlphaFraction).isWithin(FLOAT_TOLERANCE).of(0.1f) - - controller.setExpansion(0.95f) - expect.that(view.backgroundAlphaFraction).isWithin(FLOAT_TOLERANCE).of(0.5f) - - controller.setExpansion(1f) - expect.that(view.backgroundAlphaFraction).isEqualTo(1f) - } - - @Test - fun setExpansion_inSingleShade_alphaFollowsExpansion_with_0_9_delay() { - disableSplitShade() - - controller.setExpansion(0f) - expect.that(view.alpha).isEqualTo(0f) - - controller.setExpansion(0.5f) - expect.that(view.alpha).isEqualTo(0f) - - controller.setExpansion(0.9f) - expect.that(view.alpha).isEqualTo(0f) - - controller.setExpansion(0.91f) - expect.that(view.alpha).isWithin(FLOAT_TOLERANCE).of(0.1f) - - controller.setExpansion(0.95f) - expect.that(view.alpha).isWithin(FLOAT_TOLERANCE).of(0.5f) - - controller.setExpansion(1f) - expect.that(view.alpha).isEqualTo(1f) - } - - @Test - fun setExpansion_inSingleShade_backgroundAlphaAlways1() { - disableSplitShade() - - controller.setExpansion(0f) - expect.that(view.backgroundAlphaFraction).isEqualTo(1f) - - controller.setExpansion(0.5f) - expect.that(view.backgroundAlphaFraction).isEqualTo(1f) - - controller.setExpansion(1f) - expect.that(view.backgroundAlphaFraction).isEqualTo(1f) - } - - private fun setVisibilities( - securityFooterVisible: Boolean, - fgsFooterVisible: Boolean, - listener: VisibilityChangedDispatcher.OnVisibilityChangedListener - ) { - securityFooter.visibility = if (securityFooterVisible) View.VISIBLE else View.GONE - listener.onVisibilityChanged(securityFooter.visibility) - fgsFooter.visibility = if (fgsFooterVisible) View.VISIBLE else View.GONE - listener.onVisibilityChanged(fgsFooter.visibility) - } - - private fun inflateView(): FooterActionsView { - return LayoutInflater.from(context).inflate(R.layout.footer_actions, null) - as FooterActionsView - } - - private fun constructFooterActionsController(view: FooterActionsView): FooterActionsController { - return FooterActionsController( - view, - multiUserSwitchControllerFactory, - activityStarter, - userManager, - userTracker, - userInfoController, - deviceProvisionedController, - securityFooterController, - fgsManagerController, - falsingManager, - metricsLogger, - globalActionsDialogProvider, - uiEventLogger, - showPMLiteButton = true, - fakeSettings, - Handler(testableLooper.looper), - configurationController) - } - - private fun enableSplitShade() { - setSplitShadeEnabled(true) - } - - private fun disableSplitShade() { - setSplitShadeEnabled(false) - } - - private fun setSplitShadeEnabled(enabled: Boolean) { - overrideResource(R.bool.config_use_split_notification_shade, enabled) - configurationController.notifyConfigurationChanged() - } -} - -private const val FLOAT_TOLERANCE = 0.01f - -private val View.backgroundAlphaFraction: Float? - get() { - return if (background != null) { - background.alpha / 255f - } else { - null - } - } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index aedb9354e6db..ffe918d36d6f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -32,6 +33,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.Nullable; import android.app.Fragment; import android.content.Context; import android.graphics.Rect; @@ -42,6 +44,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import androidx.lifecycle.Lifecycle; import androidx.test.filters.SmallTest; import com.android.keyguard.BouncerPanelExpansionCalculator; @@ -50,12 +53,12 @@ import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.media.controls.ui.MediaHost; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSFragmentComponent; import com.android.systemui.qs.external.TileServiceRequestController; +import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder; import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; @@ -99,6 +102,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { @Mock private QSAnimator mQSAnimator; @Mock private SysuiStatusBarStateController mStatusBarStateController; @Mock private QSSquishinessController mSquishinessController; + @Mock private FooterActionsViewModel mFooterActionsViewModel; + @Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory; private View mQsFragmentView; public QSFragmentTest() { @@ -245,7 +250,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation, squishinessFraction); - verify(mQSFooterActionController).setExpansion(panelExpansionFraction); + verify(mFooterActionsViewModel).onQuickSettingsExpansionChanged( + panelExpansionFraction, /* isInSplitShade= */ true); } @Test @@ -262,7 +268,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation, squishinessFraction); - verify(mQSFooterActionController).setExpansion(expansion); + verify(mFooterActionsViewModel).onQuickSettingsExpansionChanged( + expansion, /* isInSplitShade= */ false); } @Test @@ -379,6 +386,13 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { assertThat(mQsFragmentView.getTranslationY()).isEqualTo(0); } + private Lifecycle.State getListeningAndVisibilityLifecycleState() { + return getFragment() + .getListeningAndVisibilityLifecycleOwner() + .getLifecycle() + .getCurrentState(); + } + @Test public void setListeningFalse_notVisible() { QSFragment fragment = resumeAndGetFragment(); @@ -387,7 +401,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { fragment.setListening(false); verify(mQSContainerImplController).setListening(false); - verify(mQSFooterActionController).setListening(false); + assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.CREATED); verify(mQSPanelController).setListening(eq(false), anyBoolean()); } @@ -399,7 +413,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { fragment.setListening(true); verify(mQSContainerImplController).setListening(false); - verify(mQSFooterActionController).setListening(false); + assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.STARTED); verify(mQSPanelController).setListening(eq(false), anyBoolean()); } @@ -411,7 +425,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { fragment.setListening(false); verify(mQSContainerImplController).setListening(false); - verify(mQSFooterActionController).setListening(false); + assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.CREATED); verify(mQSPanelController).setListening(eq(false), anyBoolean()); } @@ -423,7 +437,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { fragment.setListening(true); verify(mQSContainerImplController).setListening(true); - verify(mQSFooterActionController).setListening(true); + assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.RESUMED); verify(mQSPanelController).setListening(eq(true), anyBoolean()); } @@ -480,7 +494,6 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { setUpOther(); FakeFeatureFlags featureFlags = new FakeFeatureFlags(); - featureFlags.set(Flags.NEW_FOOTER_ACTIONS, false); return new QSFragment( new RemoteInputQuickSettingsDisabler( context, commandQueue, mock(ConfigurationController.class)), @@ -495,8 +508,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { mFalsingManager, mock(DumpManager.class), featureFlags, - mock(NewFooterActionsController.class), - mock(FooterActionsViewModel.Factory.class)); + mock(FooterActionsController.class), + mFooterActionsViewModelFactory); } private void setUpOther() { @@ -505,6 +518,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { when(mQSContainerImplController.getView()).thenReturn(mContainer); when(mQSPanelController.getTileLayout()).thenReturn(mQQsTileLayout); when(mQuickQSPanelController.getTileLayout()).thenReturn(mQsTileLayout); + when(mFooterActionsViewModelFactory.create(any())).thenReturn(mFooterActionsViewModel); } private void setUpMedia() { @@ -519,15 +533,40 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { .thenReturn(mQSPanelScrollView); when(mQsFragmentView.findViewById(R.id.header)).thenReturn(mHeader); when(mQsFragmentView.findViewById(android.R.id.edit)).thenReturn(new View(mContext)); + when(mQsFragmentView.findViewById(R.id.qs_footer_actions)).thenAnswer( + invocation -> FooterActionsViewBinder.create(mContext)); } private void setUpInflater() { + LayoutInflater realInflater = LayoutInflater.from(mContext); + when(mLayoutInflater.cloneInContext(any(Context.class))).thenReturn(mLayoutInflater); - when(mLayoutInflater.inflate(anyInt(), any(ViewGroup.class), anyBoolean())) - .thenReturn(mQsFragmentView); + when(mLayoutInflater.inflate(anyInt(), nullable(ViewGroup.class), anyBoolean())) + .thenAnswer((invocation) -> inflate(realInflater, (int) invocation.getArgument(0), + (ViewGroup) invocation.getArgument(1), + (boolean) invocation.getArgument(2))); + when(mLayoutInflater.inflate(anyInt(), nullable(ViewGroup.class))) + .thenAnswer((invocation) -> inflate(realInflater, (int) invocation.getArgument(0), + (ViewGroup) invocation.getArgument(1))); mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, mLayoutInflater); } + private View inflate(LayoutInflater realInflater, int layoutRes, @Nullable ViewGroup root) { + return inflate(realInflater, layoutRes, root, root != null); + } + + private View inflate(LayoutInflater realInflater, int layoutRes, @Nullable ViewGroup root, + boolean attachToRoot) { + if (layoutRes == R.layout.footer_actions + || layoutRes == R.layout.footer_actions_text_button + || layoutRes == R.layout.footer_actions_number_button + || layoutRes == R.layout.footer_actions_icon_button) { + return realInflater.inflate(layoutRes, root, attachToRoot); + } + + return mQsFragmentView; + } + private void setupQsComponent() { when(mQsComponentFactory.create(any(QSFragment.class))).thenReturn(mQsFragmentComponent); when(mQsFragmentComponent.getQSPanelController()).thenReturn(mQSPanelController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java index 906c20b1d032..c656d6dd1a35 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java @@ -17,23 +17,24 @@ package com.android.systemui.qs; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.IdRes; import android.app.AlertDialog; import android.app.admin.DevicePolicyManager; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.DialogInterface; -import android.content.Intent; import android.content.pm.UserInfo; import android.graphics.drawable.Drawable; import android.graphics.drawable.VectorDrawable; @@ -42,27 +43,27 @@ import android.os.Looper; import android.provider.Settings; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; -import android.testing.LayoutInflaterBuilder; -import android.testing.TestableImageView; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; -import android.testing.ViewUtils; import android.text.SpannableStringBuilder; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; import android.widget.TextView; +import androidx.annotation.Nullable; + import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogLaunchAnimator; +import com.android.systemui.animation.Expandable; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.common.shared.model.Icon; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig; +import com.android.systemui.security.data.model.SecurityModel; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.SecurityController; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -71,8 +72,6 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import java.util.concurrent.atomic.AtomicInteger; - /* * Compile and run the whole SystemUI test suite: runtest --path frameworks/base/packages/SystemUI/tests @@ -96,11 +95,6 @@ public class QSSecurityFooterTest extends SysuiTestCase { new ComponentName("TestDPC", "Test"); private static final int DEFAULT_ICON_ID = R.drawable.ic_info_outline; - private ViewGroup mRootView; - private ViewGroup mSecurityFooterView; - private TextView mFooterText; - private TestableImageView mPrimaryFooterIcon; - private QSSecurityFooter mFooter; private QSSecurityFooterUtils mFooterUtils; @Mock private SecurityController mSecurityController; @@ -122,58 +116,53 @@ public class QSSecurityFooterTest extends SysuiTestCase { Looper looper = mTestableLooper.getLooper(); Handler mainHandler = new Handler(looper); when(mUserTracker.getUserInfo()).thenReturn(mock(UserInfo.class)); - mSecurityFooterView = (ViewGroup) new LayoutInflaterBuilder(mContext) - .replace("ImageView", TestableImageView.class) - .build().inflate(R.layout.quick_settings_security_footer, null, false); mFooterUtils = new QSSecurityFooterUtils(getContext(), getContext().getSystemService(DevicePolicyManager.class), mUserTracker, mainHandler, mActivityStarter, mSecurityController, looper, mDialogLaunchAnimator); - mFooter = new QSSecurityFooter(mSecurityFooterView, mainHandler, mSecurityController, - looper, mBroadcastDispatcher, mFooterUtils); - mFooterText = mSecurityFooterView.findViewById(R.id.footer_text); - mPrimaryFooterIcon = mSecurityFooterView.findViewById(R.id.primary_footer_icon); when(mSecurityController.getDeviceOwnerComponentOnAnyUser()) .thenReturn(DEVICE_OWNER_COMPONENT); when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT)) .thenReturn(DEVICE_OWNER_TYPE_DEFAULT); + } - // mSecurityFooterView must have a ViewGroup parent so that - // DialogLaunchAnimator.Controller.fromView() does not return null. - mRootView = new FrameLayout(mContext); - mRootView.addView(mSecurityFooterView); - ViewUtils.attachView(mRootView); + @Nullable + private SecurityButtonConfig getButtonConfig() { + SecurityModel securityModel = SecurityModel.create(mSecurityController); + return mFooterUtils.getButtonConfig(securityModel); + } + + private void assertIsDefaultIcon(Icon icon) { + assertIsIconResource(icon, DEFAULT_ICON_ID); + } - mFooter.init(); + private void assertIsIconResource(Icon icon, @IdRes int res) { + assertThat(icon).isInstanceOf(Icon.Resource.class); + assertEquals(res, ((Icon.Resource) icon).getRes()); } - @After - public void tearDown() { - ViewUtils.detachView(mRootView); + private void assertIsIconDrawable(Icon icon, Drawable drawable) { + assertThat(icon).isInstanceOf(Icon.Loaded.class); + assertEquals(drawable, ((Icon.Loaded) icon).getDrawable()); } @Test public void testUnmanaged() { when(mSecurityController.isDeviceManaged()).thenReturn(false); when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(false); - mFooter.refreshState(); - - TestableLooper.get(this).processAllMessages(); - assertEquals(View.GONE, mSecurityFooterView.getVisibility()); + assertNull(getButtonConfig()); } @Test public void testManagedNoOwnerName() { when(mSecurityController.isDeviceManaged()).thenReturn(true); when(mSecurityController.getDeviceOwnerOrganizationName()).thenReturn(null); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); assertEquals(mContext.getString(R.string.quick_settings_disclosure_management), - mFooterText.getText()); - assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility()); - assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility()); - assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource()); + buttonConfig.getText()); + assertIsDefaultIcon(buttonConfig.getIcon()); } @Test @@ -181,15 +170,13 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.isDeviceManaged()).thenReturn(true); when(mSecurityController.getDeviceOwnerOrganizationName()) .thenReturn(MANAGING_ORGANIZATION); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_management, - MANAGING_ORGANIZATION), - mFooterText.getText()); - assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility()); - assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility()); - assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource()); + MANAGING_ORGANIZATION), + buttonConfig.getText()); + assertIsDefaultIcon(buttonConfig.getIcon()); } @Test @@ -200,15 +187,13 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT)) .thenReturn(DEVICE_OWNER_TYPE_FINANCED); - mFooter.refreshState(); - - TestableLooper.get(this).processAllMessages(); + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); assertEquals(mContext.getString( - R.string.quick_settings_financed_disclosure_named_management, - MANAGING_ORGANIZATION), mFooterText.getText()); - assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility()); - assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility()); - assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource()); + R.string.quick_settings_financed_disclosure_named_management, + MANAGING_ORGANIZATION), + buttonConfig.getText()); + assertIsDefaultIcon(buttonConfig.getIcon()); } @Test @@ -220,21 +205,16 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mUserTracker.getUserInfo()).thenReturn(mockUserInfo); Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 1); - mFooter.refreshState(); - - TestableLooper.get(this).processAllMessages(); - assertEquals(View.GONE, mSecurityFooterView.getVisibility()); + assertNull(getButtonConfig()); } @Test public void testUntappableView_profileOwnerOfOrgOwnedDevice() { when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true); - mFooter.refreshState(); - - TestableLooper.get(this).processAllMessages(); - assertFalse(mSecurityFooterView.isClickable()); - assertEquals(View.GONE, mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility()); + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); + assertFalse(buttonConfig.isClickable()); } @Test @@ -244,12 +224,9 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.isWorkProfileOn()).thenReturn(true); when(mSecurityController.hasWorkProfile()).thenReturn(true); - mFooter.refreshState(); - - TestableLooper.get(this).processAllMessages(); - assertTrue(mSecurityFooterView.isClickable()); - assertEquals(View.VISIBLE, - mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility()); + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); + assertTrue(buttonConfig.isClickable()); } @Test @@ -258,35 +235,31 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true); when(mSecurityController.isWorkProfileOn()).thenReturn(false); - mFooter.refreshState(); - - TestableLooper.get(this).processAllMessages(); - assertFalse(mSecurityFooterView.isClickable()); - assertEquals(View.GONE, mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility()); + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); + assertFalse(buttonConfig.isClickable()); } @Test public void testNetworkLoggingEnabled_deviceOwner() { when(mSecurityController.isDeviceManaged()).thenReturn(true); when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring), - mFooterText.getText()); - assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility()); - assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource()); + buttonConfig.getText()); + assertIsDefaultIcon(buttonConfig.getIcon()); // Same situation, but with organization name set when(mSecurityController.getDeviceOwnerOrganizationName()) .thenReturn(MANAGING_ORGANIZATION); - mFooter.refreshState(); - - TestableLooper.get(this).processAllMessages(); + buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); assertEquals(mContext.getString( - R.string.quick_settings_disclosure_named_management_monitoring, - MANAGING_ORGANIZATION), - mFooterText.getText()); + R.string.quick_settings_disclosure_named_management_monitoring, + MANAGING_ORGANIZATION), + buttonConfig.getText()); } @Test @@ -294,12 +267,12 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.hasWorkProfile()).thenReturn(true); when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true); when(mSecurityController.isWorkProfileOn()).thenReturn(true); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); assertEquals(mContext.getString( - R.string.quick_settings_disclosure_managed_profile_network_activity), - mFooterText.getText()); + R.string.quick_settings_disclosure_managed_profile_network_activity), + buttonConfig.getText()); } @Test @@ -307,21 +280,19 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.hasWorkProfile()).thenReturn(true); when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true); when(mSecurityController.isWorkProfileOn()).thenReturn(false); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); - assertEquals("", mFooterText.getText()); + assertNull(getButtonConfig()); } @Test public void testManagedCACertsInstalled() { when(mSecurityController.isDeviceManaged()).thenReturn(true); when(mSecurityController.hasCACertInCurrentUser()).thenReturn(true); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring), - mFooterText.getText()); + buttonConfig.getText()); } @Test @@ -329,25 +300,23 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.isDeviceManaged()).thenReturn(true); when(mSecurityController.isVpnEnabled()).thenReturn(true); when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_named_vpn, - VPN_PACKAGE), - mFooterText.getText()); - assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility()); - assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource()); + VPN_PACKAGE), + buttonConfig.getText()); + assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic); // Same situation, but with organization name set when(mSecurityController.getDeviceOwnerOrganizationName()) .thenReturn(MANAGING_ORGANIZATION); - mFooter.refreshState(); - - TestableLooper.get(this).processAllMessages(); + buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); assertEquals(mContext.getString( - R.string.quick_settings_disclosure_named_management_named_vpn, - MANAGING_ORGANIZATION, VPN_PACKAGE), - mFooterText.getText()); + R.string.quick_settings_disclosure_named_management_named_vpn, + MANAGING_ORGANIZATION, VPN_PACKAGE), + buttonConfig.getText()); } @Test @@ -356,23 +325,21 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.isVpnEnabled()).thenReturn(true); when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE); when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_vpns), - mFooterText.getText()); - assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility()); - assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource()); + buttonConfig.getText()); + assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic); // Same situation, but with organization name set when(mSecurityController.getDeviceOwnerOrganizationName()) .thenReturn(MANAGING_ORGANIZATION); - mFooter.refreshState(); - - TestableLooper.get(this).processAllMessages(); + buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_management_vpns, MANAGING_ORGANIZATION), - mFooterText.getText()); + buttonConfig.getText()); } @Test @@ -381,13 +348,12 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true); when(mSecurityController.isVpnEnabled()).thenReturn(true); when(mSecurityController.getPrimaryVpnName()).thenReturn("VPN Test App"); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); - assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility()); - assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource()); + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); + assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic); assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring), - mFooterText.getText()); + buttonConfig.getText()); } @Test @@ -395,24 +361,23 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.isDeviceManaged()).thenReturn(false); when(mSecurityController.hasCACertInWorkProfile()).thenReturn(true); when(mSecurityController.isWorkProfileOn()).thenReturn(true); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); - assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource()); + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); + assertIsDefaultIcon(buttonConfig.getIcon()); assertEquals(mContext.getString( R.string.quick_settings_disclosure_managed_profile_monitoring), - mFooterText.getText()); + buttonConfig.getText()); // Same situation, but with organization name set when(mSecurityController.getWorkProfileOrganizationName()) .thenReturn(MANAGING_ORGANIZATION); - mFooter.refreshState(); - - TestableLooper.get(this).processAllMessages(); + buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); assertEquals(mContext.getString( R.string.quick_settings_disclosure_named_managed_profile_monitoring, MANAGING_ORGANIZATION), - mFooterText.getText()); + buttonConfig.getText()); } @Test @@ -420,22 +385,20 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.isDeviceManaged()).thenReturn(false); when(mSecurityController.hasCACertInWorkProfile()).thenReturn(true); when(mSecurityController.isWorkProfileOn()).thenReturn(false); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); - assertEquals("", mFooterText.getText()); + assertNull(getButtonConfig()); } @Test public void testCACertsInstalled() { when(mSecurityController.isDeviceManaged()).thenReturn(false); when(mSecurityController.hasCACertInCurrentUser()).thenReturn(true); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); - assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource()); + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); + assertIsDefaultIcon(buttonConfig.getIcon()); assertEquals(mContext.getString(R.string.quick_settings_disclosure_monitoring), - mFooterText.getText()); + buttonConfig.getText()); } @Test @@ -443,12 +406,12 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.isVpnEnabled()).thenReturn(true); when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE); when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); - assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource()); + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); + assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic); assertEquals(mContext.getString(R.string.quick_settings_disclosure_vpns), - mFooterText.getText()); + buttonConfig.getText()); } @Test @@ -456,14 +419,14 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.isVpnEnabled()).thenReturn(true); when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2); when(mSecurityController.isWorkProfileOn()).thenReturn(true); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); - assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource()); + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); + assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic); assertEquals(mContext.getString( R.string.quick_settings_disclosure_managed_profile_named_vpn, VPN_PACKAGE_2), - mFooterText.getText()); + buttonConfig.getText()); } @Test @@ -471,22 +434,19 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.isVpnEnabled()).thenReturn(true); when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2); when(mSecurityController.isWorkProfileOn()).thenReturn(false); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); - assertEquals("", mFooterText.getText()); + assertNull(getButtonConfig()); } @Test public void testProfileOwnerOfOrganizationOwnedDeviceNoName() { when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); - + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); assertEquals(mContext.getString( R.string.quick_settings_disclosure_management), - mFooterText.getText()); + buttonConfig.getText()); } @Test @@ -495,35 +455,33 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.getWorkProfileOrganizationName()) .thenReturn(MANAGING_ORGANIZATION); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); - + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); assertEquals(mContext.getString( R.string.quick_settings_disclosure_named_management, MANAGING_ORGANIZATION), - mFooterText.getText()); + buttonConfig.getText()); } @Test public void testVpnEnabled() { when(mSecurityController.isVpnEnabled()).thenReturn(true); when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); - assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource()); + SecurityButtonConfig buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); + assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic); assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_vpn, VPN_PACKAGE), - mFooterText.getText()); + buttonConfig.getText()); when(mSecurityController.hasWorkProfile()).thenReturn(true); - mFooter.refreshState(); - - TestableLooper.get(this).processAllMessages(); + buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); assertEquals(mContext.getString( R.string.quick_settings_disclosure_personal_profile_named_vpn, VPN_PACKAGE), - mFooterText.getText()); + buttonConfig.getText()); } @Test @@ -687,19 +645,6 @@ public class QSSecurityFooterTest extends SysuiTestCase { } @Test - public void testNoClickWhenGone() { - mFooter.refreshState(); - - TestableLooper.get(this).processAllMessages(); - - assertFalse(mFooter.hasFooter()); - mFooter.onClick(mFooter.getView()); - - // Proxy for dialog being created - verify(mDialogLaunchAnimator, never()).showFromView(any(), any()); - } - - @Test public void testParentalControls() { // Make sure the security footer is visible, so that the images are updated. when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true); @@ -707,29 +652,26 @@ public class QSSecurityFooterTest extends SysuiTestCase { // We use the default icon when there is no admin icon. when(mSecurityController.getIcon(any())).thenReturn(null); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); + SecurityButtonConfig buttonConfig = getButtonConfig(); assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls), - mFooterText.getText()); - assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource()); + buttonConfig.getText()); + assertIsDefaultIcon(buttonConfig.getIcon()); Drawable testDrawable = new VectorDrawable(); when(mSecurityController.getIcon(any())).thenReturn(testDrawable); assertNotNull(mSecurityController.getIcon(null)); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); - + buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls), - mFooterText.getText()); - assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility()); - assertEquals(testDrawable, mPrimaryFooterIcon.getDrawable()); + buttonConfig.getText()); + assertIsIconDrawable(buttonConfig.getIcon(), testDrawable); // Ensure the primary icon is back to default after parental controls are gone when(mSecurityController.isParentalControlsEnabled()).thenReturn(false); - mFooter.refreshState(); - TestableLooper.get(this).processAllMessages(); - assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource()); + buttonConfig = getButtonConfig(); + assertNotNull(buttonConfig); + assertIsDefaultIcon(buttonConfig.getIcon()); } @Test @@ -743,16 +685,6 @@ public class QSSecurityFooterTest extends SysuiTestCase { } @Test - public void testDialogUsesDialogLauncher() { - when(mSecurityController.isDeviceManaged()).thenReturn(true); - mFooter.onClick(mSecurityFooterView); - - mTestableLooper.processAllMessages(); - - verify(mDialogLaunchAnimator).show(any(), any()); - } - - @Test public void testCreateDialogViewForFinancedDevice() { when(mSecurityController.isDeviceManaged()).thenReturn(true); when(mSecurityController.getDeviceOwnerOrganizationName()) @@ -782,7 +714,10 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT)) .thenReturn(DEVICE_OWNER_TYPE_FINANCED); - mFooter.showDeviceMonitoringDialog(); + Expandable expandable = mock(Expandable.class); + when(expandable.dialogLaunchController(any())).thenReturn( + mock(DialogLaunchAnimator.Controller.class)); + mFooterUtils.showDeviceMonitoringDialog(getContext(), expandable); ArgumentCaptor<AlertDialog> dialogCaptor = ArgumentCaptor.forClass(AlertDialog.class); mTestableLooper.processAllMessages(); @@ -797,47 +732,6 @@ public class QSSecurityFooterTest extends SysuiTestCase { dialog.dismiss(); } - @Test - public void testVisibilityListener() { - final AtomicInteger lastVisibility = new AtomicInteger(-1); - VisibilityChangedDispatcher.OnVisibilityChangedListener listener = lastVisibility::set; - - mFooter.setOnVisibilityChangedListener(listener); - - when(mSecurityController.isDeviceManaged()).thenReturn(true); - mFooter.refreshState(); - mTestableLooper.processAllMessages(); - assertEquals(View.VISIBLE, lastVisibility.get()); - - when(mSecurityController.isDeviceManaged()).thenReturn(false); - mFooter.refreshState(); - mTestableLooper.processAllMessages(); - assertEquals(View.GONE, lastVisibility.get()); - } - - @Test - public void testBroadcastShowsDialog() { - // Setup dialog content - when(mSecurityController.isDeviceManaged()).thenReturn(true); - when(mSecurityController.getDeviceOwnerOrganizationName()) - .thenReturn(MANAGING_ORGANIZATION); - when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT)) - .thenReturn(DEVICE_OWNER_TYPE_FINANCED); - - ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class); - verify(mBroadcastDispatcher).registerReceiverWithHandler(captor.capture(), any(), any(), - any()); - - // Pretend view is not attached anymore. - mRootView.removeView(mSecurityFooterView); - captor.getValue().onReceive(mContext, - new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG)); - mTestableLooper.processAllMessages(); - - assertTrue(mFooterUtils.getDialog().isShowing()); - mFooterUtils.getDialog().dismiss(); - } - private CharSequence addLink(CharSequence description) { final SpannableStringBuilder message = new SpannableStringBuilder(); message.append(description); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt index 47afa70fa84b..01411c9e7f04 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt @@ -385,4 +385,86 @@ class FooterActionsViewModelTest : SysuiTestCase() { underTest.onVisibilityChangeRequested(visible = true) assertThat(underTest.isVisible.value).isTrue() } + + @Test + fun alpha_inSplitShade_followsExpansion() { + val underTest = utils.footerActionsViewModel() + + underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = true) + assertThat(underTest.alpha.value).isEqualTo(0f) + + underTest.onQuickSettingsExpansionChanged(0.25f, isInSplitShade = true) + assertThat(underTest.alpha.value).isEqualTo(0.25f) + + underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = true) + assertThat(underTest.alpha.value).isEqualTo(0.5f) + + underTest.onQuickSettingsExpansionChanged(0.75f, isInSplitShade = true) + assertThat(underTest.alpha.value).isEqualTo(0.75f) + + underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = true) + assertThat(underTest.alpha.value).isEqualTo(1f) + } + + @Test + fun backgroundAlpha_inSplitShade_followsExpansion_with_0_99_delay() { + val underTest = utils.footerActionsViewModel() + val floatTolerance = 0.01f + + underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = true) + assertThat(underTest.backgroundAlpha.value).isEqualTo(0f) + + underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = true) + assertThat(underTest.backgroundAlpha.value).isEqualTo(0f) + + underTest.onQuickSettingsExpansionChanged(0.9f, isInSplitShade = true) + assertThat(underTest.backgroundAlpha.value).isEqualTo(0f) + + underTest.onQuickSettingsExpansionChanged(0.991f, isInSplitShade = true) + assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.1f) + + underTest.onQuickSettingsExpansionChanged(0.995f, isInSplitShade = true) + assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.5f) + + underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = true) + assertThat(underTest.backgroundAlpha.value).isEqualTo(1f) + } + + @Test + fun alpha_inSingleShade_followsExpansion_with_0_9_delay() { + val underTest = utils.footerActionsViewModel() + val floatTolerance = 0.01f + + underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = false) + assertThat(underTest.alpha.value).isEqualTo(0f) + + underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = false) + assertThat(underTest.alpha.value).isEqualTo(0f) + + underTest.onQuickSettingsExpansionChanged(0.9f, isInSplitShade = false) + assertThat(underTest.alpha.value).isEqualTo(0f) + + underTest.onQuickSettingsExpansionChanged(0.91f, isInSplitShade = false) + assertThat(underTest.alpha.value).isWithin(floatTolerance).of(0.1f) + + underTest.onQuickSettingsExpansionChanged(0.95f, isInSplitShade = false) + assertThat(underTest.alpha.value).isWithin(floatTolerance).of(0.5f) + + underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = false) + assertThat(underTest.alpha.value).isEqualTo(1f) + } + + @Test + fun backgroundAlpha_inSingleShade_always1() { + val underTest = utils.footerActionsViewModel() + + underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = false) + assertThat(underTest.backgroundAlpha.value).isEqualTo(1f) + + underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = false) + assertThat(underTest.backgroundAlpha.value).isEqualTo(1f) + + underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = false) + assertThat(underTest.backgroundAlpha.value).isEqualTo(1f) + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt index b31f119b7a7c..ced7955100f7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt @@ -77,7 +77,5 @@ class FakeFgsManagerController( dialogDismissedListeners.remove(listener) } - override fun shouldUpdateFooterVisibility(): Boolean = false - override fun visibleButtonsCount(): Int = 0 } |