summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/res-keyguard/layout/fgs_footer.xml105
-rw-r--r--packages/SystemUI/res-keyguard/layout/footer_actions.xml76
-rw-r--r--packages/SystemUI/res/layout/quick_settings_security_footer.xml61
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt266
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt135
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/NewFooterActionsController.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java178
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java66
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java262
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java171
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt440
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java63
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java382
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt82
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt2
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
}