Merge "Remove old footer actions implementation" into tm-qpr-dev

This commit is contained in:
TreeHugger Robot
2022-12-08 14:12:11 +00:00
committed by Android (Google) Code Review
21 changed files with 321 additions and 2077 deletions

View File

@@ -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>

View File

@@ -16,10 +16,8 @@
--> -->
<!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc --> <!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc -->
<!-- TODO(b/242040009): Clean up this file. --> <LinearLayout
<com.android.systemui.qs.FooterActionsView
xmlns:android="http://schemas.android.com/apk/res/android" 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_width="match_parent"
android:layout_height="@dimen/footer_actions_height" android:layout_height="@dimen/footer_actions_height"
android:elevation="@dimen/qs_panel_elevation" android:elevation="@dimen/qs_panel_elevation"
@@ -28,74 +26,4 @@
android:background="@drawable/qs_footer_actions_background" android:background="@drawable/qs_footer_actions_background"
android:gravity="center_vertical|end" android:gravity="center_vertical|end"
android:layout_gravity="bottom" 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>

View File

@@ -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>

View File

@@ -204,9 +204,6 @@ object Flags {
"full_screen_user_switcher" "full_screen_user_switcher"
) )
// TODO(b/254512678): Tracking Bug
@JvmField val NEW_FOOTER_ACTIONS = releasedFlag(507, "new_footer_actions")
// TODO(b/244064524): Tracking Bug // TODO(b/244064524): Tracking Bug
@JvmField @JvmField
val QS_SECONDARY_DATA_SUB_INFO = val QS_SECONDARY_DATA_SUB_INFO =

View File

@@ -122,10 +122,6 @@ interface FgsManagerController {
/** Remove a [OnDialogDismissedListener]. */ /** Remove a [OnDialogDismissedListener]. */
fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener) fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener)
/** Whether we should update the footer visibility. */
// TODO(b/242040009): Remove this.
fun shouldUpdateFooterVisibility(): Boolean
@VisibleForTesting @VisibleForTesting
fun visibleButtonsCount(): Int fun visibleButtonsCount(): Int
@@ -375,8 +371,6 @@ class FgsManagerControllerImpl @Inject constructor(
} }
} }
override fun shouldUpdateFooterVisibility() = dialog == null
override fun showDialog(expandable: Expandable?) { override fun showDialog(expandable: Expandable?) {
synchronized(lock) { synchronized(lock) {
if (dialog == null) { if (dialog == null) {

View File

@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,261 +16,17 @@
package com.android.systemui.qs package com.android.systemui.qs
import android.content.Intent import com.android.systemui.dagger.SysUISingleton
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 javax.inject.Inject import javax.inject.Inject
import javax.inject.Named
import javax.inject.Provider
/** /** Controller for the footer actions. This manages the initialization of its dependencies. */
* Manages [FooterActionsView] behaviour, both when it's placed in QS or QQS (split shade). @SysUISingleton
* Main difference between QS and QQS behaviour is condition when buttons should be visible, class FooterActionsController
* determined by [buttonsVisibleState] @Inject
*/ constructor(
@QSScope private val fgsManagerController: FgsManagerController,
// TODO(b/242040009): Remove this file. ) {
internal class FooterActionsController @Inject constructor( fun init() {
view: FooterActionsView, fgsManagerController.init()
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)
} }
} }

View File

@@ -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)"

View File

@@ -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()
}
}

View File

@@ -215,7 +215,7 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
// Some views are always full width or have dependent padding // Some views are always full width or have dependent padding
continue; continue;
} }
if (!(view instanceof FooterActionsView)) { if (view.getId() != R.id.qs_footer_actions) {
// Only padding for FooterActionsView, no margin. That way, the background goes // Only padding for FooterActionsView, no margin. That way, the background goes
// all the way to the edge. // all the way to the edge.
LayoutParams lp = (LayoutParams) view.getLayoutParams(); LayoutParams lp = (LayoutParams) view.getLayoutParams();

View File

@@ -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();
}
}

View File

@@ -33,6 +33,7 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.widget.LinearLayout;
import androidx.annotation.FloatRange; import androidx.annotation.FloatRange;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -48,7 +49,6 @@ import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.ui.MediaHost; import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS; 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 QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger;
private final QSTileHost mHost; private final QSTileHost mHost;
private final FeatureFlags mFeatureFlags; private final FeatureFlags mFeatureFlags;
private final NewFooterActionsController mNewFooterActionsController; private final FooterActionsController mFooterActionsController;
private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory; private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner; private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner;
private boolean mShowCollapsedOnKeyguard; private boolean mShowCollapsedOnKeyguard;
@@ -132,9 +132,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private QSPanelController mQSPanelController; private QSPanelController mQSPanelController;
private QuickQSPanelController mQuickQSPanelController; private QuickQSPanelController mQuickQSPanelController;
private QSCustomizerController mQSCustomizerController; private QSCustomizerController mQSCustomizerController;
@Nullable
private FooterActionsController mQSFooterActionController;
@Nullable
private FooterActionsViewModel mQSFooterActionsViewModel; private FooterActionsViewModel mQSFooterActionsViewModel;
@Nullable @Nullable
private ScrollListener mScrollListener; private ScrollListener mScrollListener;
@@ -185,7 +182,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
QSFragmentComponent.Factory qsComponentFactory, QSFragmentComponent.Factory qsComponentFactory,
QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger, QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger,
FalsingManager falsingManager, DumpManager dumpManager, FeatureFlags featureFlags, FalsingManager falsingManager, DumpManager dumpManager, FeatureFlags featureFlags,
NewFooterActionsController newFooterActionsController, FooterActionsController footerActionsController,
FooterActionsViewModel.Factory footerActionsViewModelFactory) { FooterActionsViewModel.Factory footerActionsViewModelFactory) {
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
mQsMediaHost = qsMediaHost; mQsMediaHost = qsMediaHost;
@@ -199,7 +196,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mStatusBarStateController = statusBarStateController; mStatusBarStateController = statusBarStateController;
mDumpManager = dumpManager; mDumpManager = dumpManager;
mFeatureFlags = featureFlags; mFeatureFlags = featureFlags;
mNewFooterActionsController = newFooterActionsController; mFooterActionsController = footerActionsController;
mFooterActionsViewModelFactory = footerActionsViewModelFactory; mFooterActionsViewModelFactory = footerActionsViewModelFactory;
mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner(); mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
} }
@@ -226,18 +223,12 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mQSPanelController.init(); mQSPanelController.init();
mQuickQSPanelController.init(); mQuickQSPanelController.init();
if (mFeatureFlags.isEnabled(Flags.NEW_FOOTER_ACTIONS)) { mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */
mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */ this);
this); LinearLayout footerActionsView = view.findViewById(R.id.qs_footer_actions);
FooterActionsView footerActionsView = view.findViewById(R.id.qs_footer_actions); FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner);
mListeningAndVisibilityLifecycleOwner); mFooterActionsController.init();
mNewFooterActionsController.init();
} else {
mQSFooterActionController = qsFragmentComponent.getQSFooterActionController();
mQSFooterActionController.init();
}
mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view); mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
mQSPanelScrollView.addOnLayoutChangeListener( mQSPanelScrollView.addOnLayoutChangeListener(
@@ -436,9 +427,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mContainer.disable(state1, state2, animate); mContainer.disable(state1, state2, animate);
mHeader.disable(state1, state2, animate); mHeader.disable(state1, state2, animate);
mFooter.disable(state1, state2, animate); mFooter.disable(state1, state2, animate);
if (mQSFooterActionController != null) {
mQSFooterActionController.disable(state2);
}
updateQsState(); updateQsState();
} }
@@ -457,11 +445,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
boolean footerVisible = qsPanelVisible && (mQsExpanded || !keyguardShowing boolean footerVisible = qsPanelVisible && (mQsExpanded || !keyguardShowing
|| mHeaderAnimating || mShowCollapsedOnKeyguard); || mHeaderAnimating || mShowCollapsedOnKeyguard);
mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE); mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
if (mQSFooterActionController != null) { mQSFooterActionsViewModel.onVisibilityChangeRequested(footerVisible);
mQSFooterActionController.setVisible(footerVisible);
} else {
mQSFooterActionsViewModel.onVisibilityChangeRequested(footerVisible);
}
mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard) mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
|| (mQsExpanded && !mStackScrollerOverscrolling)); || (mQsExpanded && !mStackScrollerOverscrolling));
mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE); mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE);
@@ -534,9 +518,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
} }
mFooter.setKeyguardShowing(keyguardShowing); mFooter.setKeyguardShowing(keyguardShowing);
if (mQSFooterActionController != null) {
mQSFooterActionController.setKeyguardShowing(keyguardShowing);
}
updateQsState(); updateQsState();
} }
@@ -552,9 +533,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
if (DEBUG) Log.d(TAG, "setListening " + listening); if (DEBUG) Log.d(TAG, "setListening " + listening);
mListening = listening; mListening = listening;
mQSContainerImplController.setListening(listening && mQsVisible); mQSContainerImplController.setListening(listening && mQsVisible);
if (mQSFooterActionController != null) {
mQSFooterActionController.setListening(listening && mQsVisible);
}
mListeningAndVisibilityLifecycleOwner.updateState(); mListeningAndVisibilityLifecycleOwner.updateState();
updateQsPanelControllerListening(); updateQsPanelControllerListening();
} }
@@ -665,12 +643,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion); mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
float footerActionsExpansion = float footerActionsExpansion =
onKeyguardAndExpanded ? 1 : mInSplitShade ? alphaProgress : expansion; onKeyguardAndExpanded ? 1 : mInSplitShade ? alphaProgress : expansion;
if (mQSFooterActionController != null) { mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion,
mQSFooterActionController.setExpansion(footerActionsExpansion); mInSplitShade);
} else {
mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion,
mInSplitShade);
}
mQSPanelController.setRevealExpansion(expansion); mQSPanelController.setRevealExpansion(expansion);
mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation); mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
mQuickQSPanelController.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(); boolean customizing = isCustomizing();
mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
if (mQSFooterActionController != null) { mQSFooterActionsViewModel.onVisibilityChangeRequested(!customizing);
mQSFooterActionController.setVisible(!customizing);
} else {
mQSFooterActionsViewModel.onVisibilityChangeRequested(!customizing);
}
mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE); mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
// Let the panel know the position changed and it needs to update where notifications // Let the panel know the position changed and it needs to update where notifications
// and whatnot are. // and whatnot are.
@@ -927,6 +897,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
updateShowCollapsedOnKeyguard(); updateShowCollapsedOnKeyguard();
} }
@VisibleForTesting
public ListeningAndVisibilityLifecycleOwner getListeningAndVisibilityLifecycleOwner() {
return mListeningAndVisibilityLifecycleOwner;
}
@Override @Override
public void dump(PrintWriter pw, String[] args) { public void dump(PrintWriter pw, String[] args) {
IndentingPrintWriter indentingPw = new IndentingPrintWriter(pw, /* singleIndent= */ " "); 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. * - STARTED when mListening == true && mQsVisible == false.
* - RESUMED when mListening == true && mQsVisible == true. * - RESUMED when mListening == true && mQsVisible == true.
*/ */
private class ListeningAndVisibilityLifecycleOwner implements LifecycleOwner { @VisibleForTesting
class ListeningAndVisibilityLifecycleOwner implements LifecycleOwner {
private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this); private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
private boolean mDestroyed = false; private boolean mDestroyed = false;

View File

@@ -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);
}
}
}
}

View File

@@ -28,7 +28,6 @@ import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.dagger.qualifiers.RootView; import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.qs.QS;
import com.android.systemui.privacy.OngoingPrivacyChip; import com.android.systemui.privacy.OngoingPrivacyChip;
import com.android.systemui.qs.FooterActionsView;
import com.android.systemui.qs.QSContainerImpl; import com.android.systemui.qs.QSContainerImpl;
import com.android.systemui.qs.QSFooter; import com.android.systemui.qs.QSFooter;
import com.android.systemui.qs.QSFooterView; import com.android.systemui.qs.QSFooterView;
@@ -51,8 +50,6 @@ import dagger.Provides;
*/ */
@Module @Module
public interface QSFragmentModule { 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_MEDIA_PLAYER = "qs_using_media_player";
String QS_USING_COLLAPSED_LANDSCAPE_MEDIA = "qs_using_collapsed_landscape_media"; 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); 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 @Provides
@QSScope @QSScope
@@ -144,18 +131,6 @@ public interface QSFragmentModule {
return view.findViewById(R.id.qs_customize); return view.findViewById(R.id.qs_customize);
} }
/** */
@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 @Provides
@Named(QS_USING_MEDIA_PLAYER) @Named(QS_USING_MEDIA_PLAYER)
@@ -183,15 +158,4 @@ public interface QSFragmentModule {
static StatusIconContainer providesStatusIconContainer(QuickStatusBarHeader qsHeader) { static StatusIconContainer providesStatusIconContainer(QuickStatusBarHeader qsHeader) {
return qsHeader.findViewById(R.id.statusIcons); 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);
}
} }

View File

@@ -35,35 +35,31 @@ import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.people.ui.view.PeopleViewBinder.bind 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.FooterActionsButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel 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.FooterActionsSecurityButtonViewModel
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
import kotlin.math.roundToInt
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** A ViewBinder for [FooterActionsViewBinder]. */ /** A ViewBinder for [FooterActionsViewBinder]. */
object FooterActionsViewBinder { object FooterActionsViewBinder {
/** /** Create a view that can later be [bound][bind] to a [FooterActionsViewModel]. */
* Create a [FooterActionsView] that can later be [bound][bind] to a [FooterActionsViewModel].
*/
@JvmStatic @JvmStatic
fun create(context: Context): FooterActionsView { fun create(context: Context): LinearLayout {
return LayoutInflater.from(context).inflate(R.layout.footer_actions, /* root= */ null) return LayoutInflater.from(context).inflate(R.layout.footer_actions, /* root= */ null)
as FooterActionsView as LinearLayout
} }
/** Bind [view] to [viewModel]. */ /** Bind [view] to [viewModel]. */
@JvmStatic @JvmStatic
fun bind( fun bind(
view: FooterActionsView, view: LinearLayout,
viewModel: FooterActionsViewModel, viewModel: FooterActionsViewModel,
qsVisibilityLifecycleOwner: LifecycleOwner, qsVisibilityLifecycleOwner: LifecycleOwner,
) { ) {
// Remove all children of the FooterActionsView that are used by the old implementation. view.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
// TODO(b/242040009): Clean up the XML once the old implementation is removed.
view.removeAllViews()
// Add the views used by this new implementation. // Add the views used by this new implementation.
val context = view.context val context = view.context
@@ -117,7 +113,11 @@ object FooterActionsViewBinder {
} }
launch { viewModel.alpha.collect { view.alpha = it } } 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. // Listen for model changes only when QS are visible.

View File

@@ -17,6 +17,7 @@
package com.android.systemui.security.data.model package com.android.systemui.security.data.model
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.policy.SecurityController import com.android.systemui.statusbar.policy.SecurityController
import kotlinx.coroutines.CoroutineDispatcher 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 * Important: This method should be called from a background thread as this will do a lot of
* binder calls. * binder calls.
*/ */
// TODO(b/242040009): Remove this.
@JvmStatic @JvmStatic
@VisibleForTesting
fun create(securityController: SecurityController): SecurityModel { fun create(securityController: SecurityController): SecurityModel {
val deviceAdminInfo = val deviceAdminInfo =
if (securityController.isParentalControlsEnabled) { if (securityController.isParentalControlsEnabled) {

View File

@@ -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)));
}
}

View File

@@ -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
}
}

View File

@@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock; 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.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.annotation.Nullable;
import android.app.Fragment; import android.app.Fragment;
import android.content.Context; import android.content.Context;
import android.graphics.Rect; import android.graphics.Rect;
@@ -42,6 +44,7 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.lifecycle.Lifecycle;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.android.keyguard.BouncerPanelExpansionCalculator; import com.android.keyguard.BouncerPanelExpansionCalculator;
@@ -50,12 +53,12 @@ import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.ui.MediaHost; import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSFragmentComponent; import com.android.systemui.qs.dagger.QSFragmentComponent;
import com.android.systemui.qs.external.TileServiceRequestController; 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.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarState;
@@ -99,6 +102,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
@Mock private QSAnimator mQSAnimator; @Mock private QSAnimator mQSAnimator;
@Mock private SysuiStatusBarStateController mStatusBarStateController; @Mock private SysuiStatusBarStateController mStatusBarStateController;
@Mock private QSSquishinessController mSquishinessController; @Mock private QSSquishinessController mSquishinessController;
@Mock private FooterActionsViewModel mFooterActionsViewModel;
@Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
private View mQsFragmentView; private View mQsFragmentView;
public QSFragmentTest() { public QSFragmentTest() {
@@ -245,7 +250,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation, fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
squishinessFraction); squishinessFraction);
verify(mQSFooterActionController).setExpansion(panelExpansionFraction); verify(mFooterActionsViewModel).onQuickSettingsExpansionChanged(
panelExpansionFraction, /* isInSplitShade= */ true);
} }
@Test @Test
@@ -262,7 +268,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation, fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
squishinessFraction); squishinessFraction);
verify(mQSFooterActionController).setExpansion(expansion); verify(mFooterActionsViewModel).onQuickSettingsExpansionChanged(
expansion, /* isInSplitShade= */ false);
} }
@Test @Test
@@ -379,6 +386,13 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
assertThat(mQsFragmentView.getTranslationY()).isEqualTo(0); assertThat(mQsFragmentView.getTranslationY()).isEqualTo(0);
} }
private Lifecycle.State getListeningAndVisibilityLifecycleState() {
return getFragment()
.getListeningAndVisibilityLifecycleOwner()
.getLifecycle()
.getCurrentState();
}
@Test @Test
public void setListeningFalse_notVisible() { public void setListeningFalse_notVisible() {
QSFragment fragment = resumeAndGetFragment(); QSFragment fragment = resumeAndGetFragment();
@@ -387,7 +401,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
fragment.setListening(false); fragment.setListening(false);
verify(mQSContainerImplController).setListening(false); verify(mQSContainerImplController).setListening(false);
verify(mQSFooterActionController).setListening(false); assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.CREATED);
verify(mQSPanelController).setListening(eq(false), anyBoolean()); verify(mQSPanelController).setListening(eq(false), anyBoolean());
} }
@@ -399,7 +413,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
fragment.setListening(true); fragment.setListening(true);
verify(mQSContainerImplController).setListening(false); verify(mQSContainerImplController).setListening(false);
verify(mQSFooterActionController).setListening(false); assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.STARTED);
verify(mQSPanelController).setListening(eq(false), anyBoolean()); verify(mQSPanelController).setListening(eq(false), anyBoolean());
} }
@@ -411,7 +425,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
fragment.setListening(false); fragment.setListening(false);
verify(mQSContainerImplController).setListening(false); verify(mQSContainerImplController).setListening(false);
verify(mQSFooterActionController).setListening(false); assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.CREATED);
verify(mQSPanelController).setListening(eq(false), anyBoolean()); verify(mQSPanelController).setListening(eq(false), anyBoolean());
} }
@@ -423,7 +437,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
fragment.setListening(true); fragment.setListening(true);
verify(mQSContainerImplController).setListening(true); verify(mQSContainerImplController).setListening(true);
verify(mQSFooterActionController).setListening(true); assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.RESUMED);
verify(mQSPanelController).setListening(eq(true), anyBoolean()); verify(mQSPanelController).setListening(eq(true), anyBoolean());
} }
@@ -480,7 +494,6 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
setUpOther(); setUpOther();
FakeFeatureFlags featureFlags = new FakeFeatureFlags(); FakeFeatureFlags featureFlags = new FakeFeatureFlags();
featureFlags.set(Flags.NEW_FOOTER_ACTIONS, false);
return new QSFragment( return new QSFragment(
new RemoteInputQuickSettingsDisabler( new RemoteInputQuickSettingsDisabler(
context, commandQueue, mock(ConfigurationController.class)), context, commandQueue, mock(ConfigurationController.class)),
@@ -495,8 +508,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
mFalsingManager, mFalsingManager,
mock(DumpManager.class), mock(DumpManager.class),
featureFlags, featureFlags,
mock(NewFooterActionsController.class), mock(FooterActionsController.class),
mock(FooterActionsViewModel.Factory.class)); mFooterActionsViewModelFactory);
} }
private void setUpOther() { private void setUpOther() {
@@ -505,6 +518,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
when(mQSContainerImplController.getView()).thenReturn(mContainer); when(mQSContainerImplController.getView()).thenReturn(mContainer);
when(mQSPanelController.getTileLayout()).thenReturn(mQQsTileLayout); when(mQSPanelController.getTileLayout()).thenReturn(mQQsTileLayout);
when(mQuickQSPanelController.getTileLayout()).thenReturn(mQsTileLayout); when(mQuickQSPanelController.getTileLayout()).thenReturn(mQsTileLayout);
when(mFooterActionsViewModelFactory.create(any())).thenReturn(mFooterActionsViewModel);
} }
private void setUpMedia() { private void setUpMedia() {
@@ -519,15 +533,40 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
.thenReturn(mQSPanelScrollView); .thenReturn(mQSPanelScrollView);
when(mQsFragmentView.findViewById(R.id.header)).thenReturn(mHeader); when(mQsFragmentView.findViewById(R.id.header)).thenReturn(mHeader);
when(mQsFragmentView.findViewById(android.R.id.edit)).thenReturn(new View(mContext)); 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() { private void setUpInflater() {
LayoutInflater realInflater = LayoutInflater.from(mContext);
when(mLayoutInflater.cloneInContext(any(Context.class))).thenReturn(mLayoutInflater); when(mLayoutInflater.cloneInContext(any(Context.class))).thenReturn(mLayoutInflater);
when(mLayoutInflater.inflate(anyInt(), any(ViewGroup.class), anyBoolean())) when(mLayoutInflater.inflate(anyInt(), nullable(ViewGroup.class), anyBoolean()))
.thenReturn(mQsFragmentView); .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); 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() { private void setupQsComponent() {
when(mQsComponentFactory.create(any(QSFragment.class))).thenReturn(mQsFragmentComponent); when(mQsComponentFactory.create(any(QSFragment.class))).thenReturn(mQsFragmentComponent);
when(mQsFragmentComponent.getQSPanelController()).thenReturn(mQSPanelController); when(mQsFragmentComponent.getQSPanelController()).thenReturn(mQSPanelController);

View File

@@ -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_DEFAULT;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; 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.assertEquals;
import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.annotation.IdRes;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.UserInfo; import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable; import android.graphics.drawable.VectorDrawable;
@@ -42,27 +43,27 @@ import android.os.Looper;
import android.provider.Settings; import android.provider.Settings;
import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner; import android.testing.AndroidTestingRunner;
import android.testing.LayoutInflaterBuilder;
import android.testing.TestableImageView;
import android.testing.TestableLooper; import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper; import android.testing.TestableLooper.RunWithLooper;
import android.testing.ViewUtils;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.Nullable;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.animation.Expandable;
import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.common.shared.model.Icon;
import com.android.systemui.plugins.ActivityStarter; 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.settings.UserTracker;
import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.statusbar.policy.SecurityController;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -71,8 +72,6 @@ import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import java.util.concurrent.atomic.AtomicInteger;
/* /*
* Compile and run the whole SystemUI test suite: * Compile and run the whole SystemUI test suite:
runtest --path frameworks/base/packages/SystemUI/tests runtest --path frameworks/base/packages/SystemUI/tests
@@ -96,11 +95,6 @@ public class QSSecurityFooterTest extends SysuiTestCase {
new ComponentName("TestDPC", "Test"); new ComponentName("TestDPC", "Test");
private static final int DEFAULT_ICON_ID = R.drawable.ic_info_outline; 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; private QSSecurityFooterUtils mFooterUtils;
@Mock @Mock
private SecurityController mSecurityController; private SecurityController mSecurityController;
@@ -122,58 +116,53 @@ public class QSSecurityFooterTest extends SysuiTestCase {
Looper looper = mTestableLooper.getLooper(); Looper looper = mTestableLooper.getLooper();
Handler mainHandler = new Handler(looper); Handler mainHandler = new Handler(looper);
when(mUserTracker.getUserInfo()).thenReturn(mock(UserInfo.class)); 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(), mFooterUtils = new QSSecurityFooterUtils(getContext(),
getContext().getSystemService(DevicePolicyManager.class), mUserTracker, getContext().getSystemService(DevicePolicyManager.class), mUserTracker,
mainHandler, mActivityStarter, mSecurityController, looper, mDialogLaunchAnimator); 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()) when(mSecurityController.getDeviceOwnerComponentOnAnyUser())
.thenReturn(DEVICE_OWNER_COMPONENT); .thenReturn(DEVICE_OWNER_COMPONENT);
when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT)) when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
.thenReturn(DEVICE_OWNER_TYPE_DEFAULT); .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);
mFooter.init();
} }
@After @Nullable
public void tearDown() { private SecurityButtonConfig getButtonConfig() {
ViewUtils.detachView(mRootView); SecurityModel securityModel = SecurityModel.create(mSecurityController);
return mFooterUtils.getButtonConfig(securityModel);
}
private void assertIsDefaultIcon(Icon icon) {
assertIsIconResource(icon, DEFAULT_ICON_ID);
}
private void assertIsIconResource(Icon icon, @IdRes int res) {
assertThat(icon).isInstanceOf(Icon.Resource.class);
assertEquals(res, ((Icon.Resource) icon).getRes());
}
private void assertIsIconDrawable(Icon icon, Drawable drawable) {
assertThat(icon).isInstanceOf(Icon.Loaded.class);
assertEquals(drawable, ((Icon.Loaded) icon).getDrawable());
} }
@Test @Test
public void testUnmanaged() { public void testUnmanaged() {
when(mSecurityController.isDeviceManaged()).thenReturn(false); when(mSecurityController.isDeviceManaged()).thenReturn(false);
when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(false); when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(false);
mFooter.refreshState(); assertNull(getButtonConfig());
TestableLooper.get(this).processAllMessages();
assertEquals(View.GONE, mSecurityFooterView.getVisibility());
} }
@Test @Test
public void testManagedNoOwnerName() { public void testManagedNoOwnerName() {
when(mSecurityController.isDeviceManaged()).thenReturn(true); when(mSecurityController.isDeviceManaged()).thenReturn(true);
when(mSecurityController.getDeviceOwnerOrganizationName()).thenReturn(null); 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), assertEquals(mContext.getString(R.string.quick_settings_disclosure_management),
mFooterText.getText()); buttonConfig.getText());
assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility()); assertIsDefaultIcon(buttonConfig.getIcon());
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
} }
@Test @Test
@@ -181,15 +170,13 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isDeviceManaged()).thenReturn(true); when(mSecurityController.isDeviceManaged()).thenReturn(true);
when(mSecurityController.getDeviceOwnerOrganizationName()) when(mSecurityController.getDeviceOwnerOrganizationName())
.thenReturn(MANAGING_ORGANIZATION); .thenReturn(MANAGING_ORGANIZATION);
mFooter.refreshState();
TestableLooper.get(this).processAllMessages(); SecurityButtonConfig buttonConfig = getButtonConfig();
assertNotNull(buttonConfig);
assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_management, assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_management,
MANAGING_ORGANIZATION), MANAGING_ORGANIZATION),
mFooterText.getText()); buttonConfig.getText());
assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility()); assertIsDefaultIcon(buttonConfig.getIcon());
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
} }
@Test @Test
@@ -200,15 +187,13 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT)) when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
.thenReturn(DEVICE_OWNER_TYPE_FINANCED); .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
mFooter.refreshState(); SecurityButtonConfig buttonConfig = getButtonConfig();
assertNotNull(buttonConfig);
TestableLooper.get(this).processAllMessages();
assertEquals(mContext.getString( assertEquals(mContext.getString(
R.string.quick_settings_financed_disclosure_named_management, R.string.quick_settings_financed_disclosure_named_management,
MANAGING_ORGANIZATION), mFooterText.getText()); MANAGING_ORGANIZATION),
assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility()); buttonConfig.getText());
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility()); assertIsDefaultIcon(buttonConfig.getIcon());
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
} }
@Test @Test
@@ -220,21 +205,16 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mUserTracker.getUserInfo()).thenReturn(mockUserInfo); when(mUserTracker.getUserInfo()).thenReturn(mockUserInfo);
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 1); Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 1);
mFooter.refreshState(); assertNull(getButtonConfig());
TestableLooper.get(this).processAllMessages();
assertEquals(View.GONE, mSecurityFooterView.getVisibility());
} }
@Test @Test
public void testUntappableView_profileOwnerOfOrgOwnedDevice() { public void testUntappableView_profileOwnerOfOrgOwnedDevice() {
when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true); when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true);
mFooter.refreshState(); SecurityButtonConfig buttonConfig = getButtonConfig();
assertNotNull(buttonConfig);
TestableLooper.get(this).processAllMessages(); assertFalse(buttonConfig.isClickable());
assertFalse(mSecurityFooterView.isClickable());
assertEquals(View.GONE, mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
} }
@Test @Test
@@ -244,12 +224,9 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isWorkProfileOn()).thenReturn(true); when(mSecurityController.isWorkProfileOn()).thenReturn(true);
when(mSecurityController.hasWorkProfile()).thenReturn(true); when(mSecurityController.hasWorkProfile()).thenReturn(true);
mFooter.refreshState(); SecurityButtonConfig buttonConfig = getButtonConfig();
assertNotNull(buttonConfig);
TestableLooper.get(this).processAllMessages(); assertTrue(buttonConfig.isClickable());
assertTrue(mSecurityFooterView.isClickable());
assertEquals(View.VISIBLE,
mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
} }
@Test @Test
@@ -258,35 +235,31 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true); when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
when(mSecurityController.isWorkProfileOn()).thenReturn(false); when(mSecurityController.isWorkProfileOn()).thenReturn(false);
mFooter.refreshState(); SecurityButtonConfig buttonConfig = getButtonConfig();
assertNotNull(buttonConfig);
TestableLooper.get(this).processAllMessages(); assertFalse(buttonConfig.isClickable());
assertFalse(mSecurityFooterView.isClickable());
assertEquals(View.GONE, mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
} }
@Test @Test
public void testNetworkLoggingEnabled_deviceOwner() { public void testNetworkLoggingEnabled_deviceOwner() {
when(mSecurityController.isDeviceManaged()).thenReturn(true); when(mSecurityController.isDeviceManaged()).thenReturn(true);
when(mSecurityController.isNetworkLoggingEnabled()).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), assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring),
mFooterText.getText()); buttonConfig.getText());
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility()); assertIsDefaultIcon(buttonConfig.getIcon());
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
// Same situation, but with organization name set // Same situation, but with organization name set
when(mSecurityController.getDeviceOwnerOrganizationName()) when(mSecurityController.getDeviceOwnerOrganizationName())
.thenReturn(MANAGING_ORGANIZATION); .thenReturn(MANAGING_ORGANIZATION);
mFooter.refreshState(); buttonConfig = getButtonConfig();
assertNotNull(buttonConfig);
TestableLooper.get(this).processAllMessages();
assertEquals(mContext.getString( assertEquals(mContext.getString(
R.string.quick_settings_disclosure_named_management_monitoring, R.string.quick_settings_disclosure_named_management_monitoring,
MANAGING_ORGANIZATION), MANAGING_ORGANIZATION),
mFooterText.getText()); buttonConfig.getText());
} }
@Test @Test
@@ -294,12 +267,12 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.hasWorkProfile()).thenReturn(true); when(mSecurityController.hasWorkProfile()).thenReturn(true);
when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true); when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
when(mSecurityController.isWorkProfileOn()).thenReturn(true); when(mSecurityController.isWorkProfileOn()).thenReturn(true);
mFooter.refreshState();
TestableLooper.get(this).processAllMessages(); SecurityButtonConfig buttonConfig = getButtonConfig();
assertNotNull(buttonConfig);
assertEquals(mContext.getString( assertEquals(mContext.getString(
R.string.quick_settings_disclosure_managed_profile_network_activity), R.string.quick_settings_disclosure_managed_profile_network_activity),
mFooterText.getText()); buttonConfig.getText());
} }
@Test @Test
@@ -307,21 +280,19 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.hasWorkProfile()).thenReturn(true); when(mSecurityController.hasWorkProfile()).thenReturn(true);
when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true); when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
when(mSecurityController.isWorkProfileOn()).thenReturn(false); when(mSecurityController.isWorkProfileOn()).thenReturn(false);
mFooter.refreshState();
TestableLooper.get(this).processAllMessages(); assertNull(getButtonConfig());
assertEquals("", mFooterText.getText());
} }
@Test @Test
public void testManagedCACertsInstalled() { public void testManagedCACertsInstalled() {
when(mSecurityController.isDeviceManaged()).thenReturn(true); when(mSecurityController.isDeviceManaged()).thenReturn(true);
when(mSecurityController.hasCACertInCurrentUser()).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), assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring),
mFooterText.getText()); buttonConfig.getText());
} }
@Test @Test
@@ -329,25 +300,23 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isDeviceManaged()).thenReturn(true); when(mSecurityController.isDeviceManaged()).thenReturn(true);
when(mSecurityController.isVpnEnabled()).thenReturn(true); when(mSecurityController.isVpnEnabled()).thenReturn(true);
when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE); 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, assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_named_vpn,
VPN_PACKAGE), VPN_PACKAGE),
mFooterText.getText()); buttonConfig.getText());
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility()); assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource());
// Same situation, but with organization name set // Same situation, but with organization name set
when(mSecurityController.getDeviceOwnerOrganizationName()) when(mSecurityController.getDeviceOwnerOrganizationName())
.thenReturn(MANAGING_ORGANIZATION); .thenReturn(MANAGING_ORGANIZATION);
mFooter.refreshState(); buttonConfig = getButtonConfig();
assertNotNull(buttonConfig);
TestableLooper.get(this).processAllMessages();
assertEquals(mContext.getString( assertEquals(mContext.getString(
R.string.quick_settings_disclosure_named_management_named_vpn, R.string.quick_settings_disclosure_named_management_named_vpn,
MANAGING_ORGANIZATION, VPN_PACKAGE), MANAGING_ORGANIZATION, VPN_PACKAGE),
mFooterText.getText()); buttonConfig.getText());
} }
@Test @Test
@@ -356,23 +325,21 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isVpnEnabled()).thenReturn(true); when(mSecurityController.isVpnEnabled()).thenReturn(true);
when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE); when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE);
when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2); 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), assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_vpns),
mFooterText.getText()); buttonConfig.getText());
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility()); assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource());
// Same situation, but with organization name set // Same situation, but with organization name set
when(mSecurityController.getDeviceOwnerOrganizationName()) when(mSecurityController.getDeviceOwnerOrganizationName())
.thenReturn(MANAGING_ORGANIZATION); .thenReturn(MANAGING_ORGANIZATION);
mFooter.refreshState(); buttonConfig = getButtonConfig();
assertNotNull(buttonConfig);
TestableLooper.get(this).processAllMessages();
assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_management_vpns, assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_management_vpns,
MANAGING_ORGANIZATION), MANAGING_ORGANIZATION),
mFooterText.getText()); buttonConfig.getText());
} }
@Test @Test
@@ -381,13 +348,12 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true); when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
when(mSecurityController.isVpnEnabled()).thenReturn(true); when(mSecurityController.isVpnEnabled()).thenReturn(true);
when(mSecurityController.getPrimaryVpnName()).thenReturn("VPN Test App"); when(mSecurityController.getPrimaryVpnName()).thenReturn("VPN Test App");
mFooter.refreshState();
TestableLooper.get(this).processAllMessages(); SecurityButtonConfig buttonConfig = getButtonConfig();
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility()); assertNotNull(buttonConfig);
assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource()); assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring), assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring),
mFooterText.getText()); buttonConfig.getText());
} }
@Test @Test
@@ -395,24 +361,23 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isDeviceManaged()).thenReturn(false); when(mSecurityController.isDeviceManaged()).thenReturn(false);
when(mSecurityController.hasCACertInWorkProfile()).thenReturn(true); when(mSecurityController.hasCACertInWorkProfile()).thenReturn(true);
when(mSecurityController.isWorkProfileOn()).thenReturn(true); when(mSecurityController.isWorkProfileOn()).thenReturn(true);
mFooter.refreshState();
TestableLooper.get(this).processAllMessages(); SecurityButtonConfig buttonConfig = getButtonConfig();
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource()); assertNotNull(buttonConfig);
assertIsDefaultIcon(buttonConfig.getIcon());
assertEquals(mContext.getString( assertEquals(mContext.getString(
R.string.quick_settings_disclosure_managed_profile_monitoring), R.string.quick_settings_disclosure_managed_profile_monitoring),
mFooterText.getText()); buttonConfig.getText());
// Same situation, but with organization name set // Same situation, but with organization name set
when(mSecurityController.getWorkProfileOrganizationName()) when(mSecurityController.getWorkProfileOrganizationName())
.thenReturn(MANAGING_ORGANIZATION); .thenReturn(MANAGING_ORGANIZATION);
mFooter.refreshState(); buttonConfig = getButtonConfig();
assertNotNull(buttonConfig);
TestableLooper.get(this).processAllMessages();
assertEquals(mContext.getString( assertEquals(mContext.getString(
R.string.quick_settings_disclosure_named_managed_profile_monitoring, R.string.quick_settings_disclosure_named_managed_profile_monitoring,
MANAGING_ORGANIZATION), MANAGING_ORGANIZATION),
mFooterText.getText()); buttonConfig.getText());
} }
@Test @Test
@@ -420,22 +385,20 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isDeviceManaged()).thenReturn(false); when(mSecurityController.isDeviceManaged()).thenReturn(false);
when(mSecurityController.hasCACertInWorkProfile()).thenReturn(true); when(mSecurityController.hasCACertInWorkProfile()).thenReturn(true);
when(mSecurityController.isWorkProfileOn()).thenReturn(false); when(mSecurityController.isWorkProfileOn()).thenReturn(false);
mFooter.refreshState();
TestableLooper.get(this).processAllMessages(); assertNull(getButtonConfig());
assertEquals("", mFooterText.getText());
} }
@Test @Test
public void testCACertsInstalled() { public void testCACertsInstalled() {
when(mSecurityController.isDeviceManaged()).thenReturn(false); when(mSecurityController.isDeviceManaged()).thenReturn(false);
when(mSecurityController.hasCACertInCurrentUser()).thenReturn(true); when(mSecurityController.hasCACertInCurrentUser()).thenReturn(true);
mFooter.refreshState();
TestableLooper.get(this).processAllMessages(); SecurityButtonConfig buttonConfig = getButtonConfig();
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource()); assertNotNull(buttonConfig);
assertIsDefaultIcon(buttonConfig.getIcon());
assertEquals(mContext.getString(R.string.quick_settings_disclosure_monitoring), assertEquals(mContext.getString(R.string.quick_settings_disclosure_monitoring),
mFooterText.getText()); buttonConfig.getText());
} }
@Test @Test
@@ -443,12 +406,12 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isVpnEnabled()).thenReturn(true); when(mSecurityController.isVpnEnabled()).thenReturn(true);
when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE); when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE);
when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2); when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2);
mFooter.refreshState();
TestableLooper.get(this).processAllMessages(); SecurityButtonConfig buttonConfig = getButtonConfig();
assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource()); assertNotNull(buttonConfig);
assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
assertEquals(mContext.getString(R.string.quick_settings_disclosure_vpns), assertEquals(mContext.getString(R.string.quick_settings_disclosure_vpns),
mFooterText.getText()); buttonConfig.getText());
} }
@Test @Test
@@ -456,14 +419,14 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isVpnEnabled()).thenReturn(true); when(mSecurityController.isVpnEnabled()).thenReturn(true);
when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2); when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2);
when(mSecurityController.isWorkProfileOn()).thenReturn(true); when(mSecurityController.isWorkProfileOn()).thenReturn(true);
mFooter.refreshState();
TestableLooper.get(this).processAllMessages(); SecurityButtonConfig buttonConfig = getButtonConfig();
assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource()); assertNotNull(buttonConfig);
assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
assertEquals(mContext.getString( assertEquals(mContext.getString(
R.string.quick_settings_disclosure_managed_profile_named_vpn, R.string.quick_settings_disclosure_managed_profile_named_vpn,
VPN_PACKAGE_2), VPN_PACKAGE_2),
mFooterText.getText()); buttonConfig.getText());
} }
@Test @Test
@@ -471,22 +434,19 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.isVpnEnabled()).thenReturn(true); when(mSecurityController.isVpnEnabled()).thenReturn(true);
when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2); when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2);
when(mSecurityController.isWorkProfileOn()).thenReturn(false); when(mSecurityController.isWorkProfileOn()).thenReturn(false);
mFooter.refreshState();
TestableLooper.get(this).processAllMessages(); assertNull(getButtonConfig());
assertEquals("", mFooterText.getText());
} }
@Test @Test
public void testProfileOwnerOfOrganizationOwnedDeviceNoName() { public void testProfileOwnerOfOrganizationOwnedDeviceNoName() {
when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true); when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true);
mFooter.refreshState(); SecurityButtonConfig buttonConfig = getButtonConfig();
TestableLooper.get(this).processAllMessages(); assertNotNull(buttonConfig);
assertEquals(mContext.getString( assertEquals(mContext.getString(
R.string.quick_settings_disclosure_management), R.string.quick_settings_disclosure_management),
mFooterText.getText()); buttonConfig.getText());
} }
@Test @Test
@@ -495,35 +455,33 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.getWorkProfileOrganizationName()) when(mSecurityController.getWorkProfileOrganizationName())
.thenReturn(MANAGING_ORGANIZATION); .thenReturn(MANAGING_ORGANIZATION);
mFooter.refreshState(); SecurityButtonConfig buttonConfig = getButtonConfig();
TestableLooper.get(this).processAllMessages(); assertNotNull(buttonConfig);
assertEquals(mContext.getString( assertEquals(mContext.getString(
R.string.quick_settings_disclosure_named_management, R.string.quick_settings_disclosure_named_management,
MANAGING_ORGANIZATION), MANAGING_ORGANIZATION),
mFooterText.getText()); buttonConfig.getText());
} }
@Test @Test
public void testVpnEnabled() { public void testVpnEnabled() {
when(mSecurityController.isVpnEnabled()).thenReturn(true); when(mSecurityController.isVpnEnabled()).thenReturn(true);
when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE); when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE);
mFooter.refreshState();
TestableLooper.get(this).processAllMessages(); SecurityButtonConfig buttonConfig = getButtonConfig();
assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource()); assertNotNull(buttonConfig);
assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_vpn, assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_vpn,
VPN_PACKAGE), VPN_PACKAGE),
mFooterText.getText()); buttonConfig.getText());
when(mSecurityController.hasWorkProfile()).thenReturn(true); when(mSecurityController.hasWorkProfile()).thenReturn(true);
mFooter.refreshState(); buttonConfig = getButtonConfig();
assertNotNull(buttonConfig);
TestableLooper.get(this).processAllMessages();
assertEquals(mContext.getString( assertEquals(mContext.getString(
R.string.quick_settings_disclosure_personal_profile_named_vpn, R.string.quick_settings_disclosure_personal_profile_named_vpn,
VPN_PACKAGE), VPN_PACKAGE),
mFooterText.getText()); buttonConfig.getText());
} }
@Test @Test
@@ -686,19 +644,6 @@ public class QSSecurityFooterTest extends SysuiTestCase {
view.findViewById(R.id.vpn_subtitle).getVisibility()); view.findViewById(R.id.vpn_subtitle).getVisibility());
} }
@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 @Test
public void testParentalControls() { public void testParentalControls() {
// Make sure the security footer is visible, so that the images are updated. // Make sure the security footer is visible, so that the images are updated.
@@ -707,29 +652,26 @@ public class QSSecurityFooterTest extends SysuiTestCase {
// We use the default icon when there is no admin icon. // We use the default icon when there is no admin icon.
when(mSecurityController.getIcon(any())).thenReturn(null); when(mSecurityController.getIcon(any())).thenReturn(null);
mFooter.refreshState(); SecurityButtonConfig buttonConfig = getButtonConfig();
TestableLooper.get(this).processAllMessages();
assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls), assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls),
mFooterText.getText()); buttonConfig.getText());
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource()); assertIsDefaultIcon(buttonConfig.getIcon());
Drawable testDrawable = new VectorDrawable(); Drawable testDrawable = new VectorDrawable();
when(mSecurityController.getIcon(any())).thenReturn(testDrawable); when(mSecurityController.getIcon(any())).thenReturn(testDrawable);
assertNotNull(mSecurityController.getIcon(null)); assertNotNull(mSecurityController.getIcon(null));
mFooter.refreshState(); buttonConfig = getButtonConfig();
TestableLooper.get(this).processAllMessages(); assertNotNull(buttonConfig);
assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls), assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls),
mFooterText.getText()); buttonConfig.getText());
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility()); assertIsIconDrawable(buttonConfig.getIcon(), testDrawable);
assertEquals(testDrawable, mPrimaryFooterIcon.getDrawable());
// Ensure the primary icon is back to default after parental controls are gone // Ensure the primary icon is back to default after parental controls are gone
when(mSecurityController.isParentalControlsEnabled()).thenReturn(false); when(mSecurityController.isParentalControlsEnabled()).thenReturn(false);
mFooter.refreshState(); buttonConfig = getButtonConfig();
TestableLooper.get(this).processAllMessages(); assertNotNull(buttonConfig);
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource()); assertIsDefaultIcon(buttonConfig.getIcon());
} }
@Test @Test
@@ -742,16 +684,6 @@ public class QSSecurityFooterTest extends SysuiTestCase {
assertEquals(PARENTAL_CONTROLS_LABEL, textView.getText()); assertEquals(PARENTAL_CONTROLS_LABEL, textView.getText());
} }
@Test
public void testDialogUsesDialogLauncher() {
when(mSecurityController.isDeviceManaged()).thenReturn(true);
mFooter.onClick(mSecurityFooterView);
mTestableLooper.processAllMessages();
verify(mDialogLaunchAnimator).show(any(), any());
}
@Test @Test
public void testCreateDialogViewForFinancedDevice() { public void testCreateDialogViewForFinancedDevice() {
when(mSecurityController.isDeviceManaged()).thenReturn(true); when(mSecurityController.isDeviceManaged()).thenReturn(true);
@@ -782,7 +714,10 @@ public class QSSecurityFooterTest extends SysuiTestCase {
when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT)) when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
.thenReturn(DEVICE_OWNER_TYPE_FINANCED); .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); ArgumentCaptor<AlertDialog> dialogCaptor = ArgumentCaptor.forClass(AlertDialog.class);
mTestableLooper.processAllMessages(); mTestableLooper.processAllMessages();
@@ -797,47 +732,6 @@ public class QSSecurityFooterTest extends SysuiTestCase {
dialog.dismiss(); 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) { private CharSequence addLink(CharSequence description) {
final SpannableStringBuilder message = new SpannableStringBuilder(); final SpannableStringBuilder message = new SpannableStringBuilder();
message.append(description); message.append(description);

View File

@@ -385,4 +385,86 @@ class FooterActionsViewModelTest : SysuiTestCase() {
underTest.onVisibilityChangeRequested(visible = true) underTest.onVisibilityChangeRequested(visible = true)
assertThat(underTest.isVisible.value).isTrue() 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)
}
} }

View File

@@ -77,7 +77,5 @@ class FakeFgsManagerController(
dialogDismissedListeners.remove(listener) dialogDismissedListeners.remove(listener)
} }
override fun shouldUpdateFooterVisibility(): Boolean = false
override fun visibleButtonsCount(): Int = 0 override fun visibleButtonsCount(): Int = 0
} }