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 -->
<!-- TODO(b/242040009): Clean up this file. -->
<com.android.systemui.qs.FooterActionsView
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/footer_actions_height"
android:elevation="@dimen/qs_panel_elevation"
@@ -28,74 +26,4 @@
android:background="@drawable/qs_footer_actions_background"
android:gravity="center_vertical|end"
android:layout_gravity="bottom"
>
<LinearLayout
android:id="@+id/security_footers_container"
android:orientation="horizontal"
android:layout_height="@dimen/qs_footer_action_button_size"
android:layout_width="0dp"
android:layout_weight="1"
/>
<!-- Negative margin equal to -->
<LinearLayout
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_marginEnd="@dimen/qs_footer_action_inset_negative"
>
<com.android.systemui.statusbar.phone.MultiUserSwitch
android:id="@id/multi_user_switch"
android:layout_width="@dimen/qs_footer_action_button_size"
android:layout_height="@dimen/qs_footer_action_button_size"
android:background="@drawable/qs_footer_action_circle"
android:focusable="true">
<ImageView
android:id="@+id/multi_user_avatar"
android:layout_width="@dimen/qs_footer_icon_size"
android:layout_height="@dimen/qs_footer_icon_size"
android:layout_gravity="center"
android:scaleType="centerInside" />
</com.android.systemui.statusbar.phone.MultiUserSwitch>
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@id/settings_button_container"
android:layout_width="@dimen/qs_footer_action_button_size"
android:layout_height="@dimen/qs_footer_action_button_size"
android:background="@drawable/qs_footer_action_circle"
android:clipChildren="false"
android:clipToPadding="false">
<com.android.systemui.statusbar.phone.SettingsButton
android:id="@+id/settings_button"
android:layout_width="@dimen/qs_footer_icon_size"
android:layout_height="@dimen/qs_footer_icon_size"
android:layout_gravity="center"
android:background="@android:color/transparent"
android:focusable="false"
android:clickable="false"
android:importantForAccessibility="yes"
android:contentDescription="@string/accessibility_quick_settings_settings"
android:scaleType="centerInside"
android:src="@drawable/ic_settings"
android:tint="?android:attr/textColorPrimary" />
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@id/pm_lite"
android:layout_width="@dimen/qs_footer_action_button_size"
android:layout_height="@dimen/qs_footer_action_button_size"
android:background="@drawable/qs_footer_action_circle_color"
android:clickable="true"
android:clipToPadding="false"
android:focusable="true"
android:padding="@dimen/qs_footer_icon_padding"
android:src="@*android:drawable/ic_lock_power_off"
android:contentDescription="@string/accessibility_quick_settings_power_menu"
android:tint="?androidprv:attr/textColorOnAccent" />
</LinearLayout>
</com.android.systemui.qs.FooterActionsView>
/>

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"
)
// TODO(b/254512678): Tracking Bug
@JvmField val NEW_FOOTER_ACTIONS = releasedFlag(507, "new_footer_actions")
// TODO(b/244064524): Tracking Bug
@JvmField
val QS_SECONDARY_DATA_SUB_INFO =

View File

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

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");
* you may not use this file except in compliance with the License.
@@ -16,261 +16,17 @@
package com.android.systemui.qs
import android.content.Intent
import android.content.res.Configuration
import android.os.Handler
import android.os.UserManager
import android.provider.Settings
import android.provider.Settings.Global.USER_SWITCHER_ENABLED
import android.view.View
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
import com.android.internal.logging.nano.MetricsProto
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
import com.android.systemui.qs.dagger.QSScope
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.MultiUserSwitchController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
import com.android.systemui.util.LargeScreenUtils
import com.android.systemui.util.ViewController
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Provider
/**
* Manages [FooterActionsView] behaviour, both when it's placed in QS or QQS (split shade).
* Main difference between QS and QQS behaviour is condition when buttons should be visible,
* determined by [buttonsVisibleState]
*/
@QSScope
// TODO(b/242040009): Remove this file.
internal class FooterActionsController @Inject constructor(
view: FooterActionsView,
multiUserSwitchControllerFactory: MultiUserSwitchController.Factory,
private val activityStarter: ActivityStarter,
private val userManager: UserManager,
private val userTracker: UserTracker,
private val userInfoController: UserInfoController,
private val deviceProvisionedController: DeviceProvisionedController,
private val securityFooterController: QSSecurityFooter,
private val fgsManagerFooterController: QSFgsManagerFooter,
private val falsingManager: FalsingManager,
private val metricsLogger: MetricsLogger,
private val globalActionsDialogProvider: Provider<GlobalActionsDialogLite>,
private val uiEventLogger: UiEventLogger,
@Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
private val globalSetting: GlobalSettings,
private val handler: Handler,
private val configurationController: ConfigurationController,
) : ViewController<FooterActionsView>(view) {
private var globalActionsDialog: GlobalActionsDialogLite? = null
private var lastExpansion = -1f
private var listening: Boolean = false
private var inSplitShade = false
private val singleShadeAnimator by lazy {
// In single shade, the actions footer should only appear at the end of the expansion,
// so that it doesn't overlap with the notifications panel.
TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).setStartDelay(0.9f).build()
}
private val splitShadeAnimator by lazy {
// The Actions footer view has its own background which is the same color as the qs panel's
// background.
// We don't want it to fade in at the same time as the rest of the panel, otherwise it is
// more opaque than the rest of the panel's background. Only applies to split shade.
val alphaAnimator = TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).build()
val bgAlphaAnimator =
TouchAnimator.Builder()
.addFloat(mView, "backgroundAlpha", 0f, 1f)
.setStartDelay(0.9f)
.build()
// In split shade, we want the actions footer to fade in exactly at the same time as the
// rest of the shade, as there is no overlap.
TouchAnimator.Builder()
.addFloat(alphaAnimator, "position", 0f, 1f)
.addFloat(bgAlphaAnimator, "position", 0f, 1f)
.build()
}
private val animators: TouchAnimator
get() = if (inSplitShade) splitShadeAnimator else singleShadeAnimator
var visible = true
set(value) {
field = value
updateVisibility()
}
private val settingsButtonContainer: View = view.findViewById(R.id.settings_button_container)
private val securityFootersContainer: ViewGroup? =
view.findViewById(R.id.security_footers_container)
private val powerMenuLite: View = view.findViewById(R.id.pm_lite)
private val multiUserSwitchController = multiUserSwitchControllerFactory.create(view)
@VisibleForTesting
internal val securityFootersSeparator = View(context).apply { visibility = View.GONE }
private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ ->
val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
mView.onUserInfoChanged(picture, isGuestUser)
}
private val multiUserSetting =
object : SettingObserver(
globalSetting, handler, USER_SWITCHER_ENABLED, userTracker.userId) {
override fun handleValueChanged(value: Int, observedChange: Boolean) {
if (observedChange) {
updateView()
}
}
}
private val onClickListener = View.OnClickListener { v ->
// Don't do anything if the tap looks suspicious.
if (!visible || falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return@OnClickListener
}
if (v === settingsButtonContainer) {
if (!deviceProvisionedController.isCurrentUserSetup) {
// If user isn't setup just unlock the device and dump them back at SUW.
activityStarter.postQSRunnableDismissingKeyguard {}
return@OnClickListener
}
metricsLogger.action(MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH)
startSettingsActivity()
} else if (v === powerMenuLite) {
uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
globalActionsDialog?.showOrHideDialog(false, true, Expandable.fromView(powerMenuLite))
}
}
private val configurationListener =
object : ConfigurationController.ConfigurationListener {
override fun onConfigChanged(newConfig: Configuration?) {
updateResources()
}
}
private fun updateResources() {
inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(resources)
}
override fun onInit() {
multiUserSwitchController.init()
securityFooterController.init()
fgsManagerFooterController.init()
}
private fun updateVisibility() {
val previousVisibility = mView.visibility
mView.visibility = if (visible) View.VISIBLE else View.INVISIBLE
if (previousVisibility != mView.visibility) updateView()
}
private fun startSettingsActivity() {
val animationController = settingsButtonContainer?.let {
ActivityLaunchAnimator.Controller.fromView(
it,
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON)
}
activityStarter.startActivity(Intent(Settings.ACTION_SETTINGS),
true /* dismissShade */, animationController)
}
@VisibleForTesting
public override fun onViewAttached() {
globalActionsDialog = globalActionsDialogProvider.get()
if (showPMLiteButton) {
powerMenuLite.visibility = View.VISIBLE
powerMenuLite.setOnClickListener(onClickListener)
} else {
powerMenuLite.visibility = View.GONE
}
settingsButtonContainer.setOnClickListener(onClickListener)
multiUserSetting.isListening = true
val securityFooter = securityFooterController.view
securityFootersContainer?.addView(securityFooter)
val separatorWidth = resources.getDimensionPixelSize(R.dimen.qs_footer_action_inset)
securityFootersContainer?.addView(securityFootersSeparator, separatorWidth, 1)
val fgsFooter = fgsManagerFooterController.view
securityFootersContainer?.addView(fgsFooter)
val visibilityListener =
VisibilityChangedDispatcher.OnVisibilityChangedListener { visibility ->
if (securityFooter.visibility == View.VISIBLE &&
fgsFooter.visibility == View.VISIBLE) {
securityFootersSeparator.visibility = View.VISIBLE
} else {
securityFootersSeparator.visibility = View.GONE
}
fgsManagerFooterController
.setCollapsed(securityFooter.visibility == View.VISIBLE)
}
securityFooterController.setOnVisibilityChangedListener(visibilityListener)
fgsManagerFooterController.setOnVisibilityChangedListener(visibilityListener)
configurationController.addCallback(configurationListener)
updateResources()
updateView()
}
private fun updateView() {
mView.updateEverything(multiUserSwitchController.isMultiUserEnabled)
}
override fun onViewDetached() {
globalActionsDialog?.destroy()
globalActionsDialog = null
setListening(false)
multiUserSetting.isListening = false
configurationController.removeCallback(configurationListener)
}
fun setListening(listening: Boolean) {
if (this.listening == listening) {
return
}
this.listening = listening
if (this.listening) {
userInfoController.addCallback(onUserInfoChangedListener)
updateView()
} else {
userInfoController.removeCallback(onUserInfoChangedListener)
}
fgsManagerFooterController.setListening(listening)
securityFooterController.setListening(listening)
}
fun disable(state2: Int) {
mView.disable(state2, multiUserSwitchController.isMultiUserEnabled)
}
fun setExpansion(headerExpansionFraction: Float) {
animators.setPosition(headerExpansionFraction)
}
fun setKeyguardShowing(showing: Boolean) {
setExpansion(lastExpansion)
/** Controller for the footer actions. This manages the initialization of its dependencies. */
@SysUISingleton
class FooterActionsController
@Inject
constructor(
private val fgsManagerController: FgsManagerController,
) {
fun init() {
fgsManagerController.init()
}
}

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
continue;
}
if (!(view instanceof FooterActionsView)) {
if (view.getId() != R.id.qs_footer_actions) {
// Only padding for FooterActionsView, no margin. That way, the background goes
// all the way to the edge.
LayoutParams lp = (LayoutParams) view.getLayoutParams();

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

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.plugins.qs.QS;
import com.android.systemui.privacy.OngoingPrivacyChip;
import com.android.systemui.qs.FooterActionsView;
import com.android.systemui.qs.QSContainerImpl;
import com.android.systemui.qs.QSFooter;
import com.android.systemui.qs.QSFooterView;
@@ -51,8 +50,6 @@ import dagger.Provides;
*/
@Module
public interface QSFragmentModule {
String QS_FGS_MANAGER_FOOTER_VIEW = "qs_fgs_manager_footer";
String QS_SECURITY_FOOTER_VIEW = "qs_security_footer";
String QS_USING_MEDIA_PLAYER = "qs_using_media_player";
String QS_USING_COLLAPSED_LANDSCAPE_MEDIA = "qs_using_collapsed_landscape_media";
@@ -119,16 +116,6 @@ public interface QSFragmentModule {
return view.findViewById(R.id.qs_footer);
}
/**
* Provides a {@link FooterActionsView}.
*
* This will replace a ViewStub either in {@link QSFooterView} or in {@link QSContainerImpl}.
*/
@Provides
static FooterActionsView providesQSFooterActionsView(@RootView View view) {
return view.findViewById(R.id.qs_footer_actions);
}
/** */
@Provides
@QSScope
@@ -144,18 +131,6 @@ public interface QSFragmentModule {
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
@Named(QS_USING_MEDIA_PLAYER)
@@ -183,15 +158,4 @@ public interface QSFragmentModule {
static StatusIconContainer providesStatusIconContainer(QuickStatusBarHeader qsHeader) {
return qsHeader.findViewById(R.id.statusIcons);
}
/** */
@Provides
@QSScope
@Named(QS_FGS_MANAGER_FOOTER_VIEW)
static View providesQSFgsManagerFooterView(
@QSThemedContext LayoutInflater layoutInflater,
FooterActionsView footerActionsView
) {
return layoutInflater.inflate(R.layout.fgs_footer, footerActionsView, false);
}
}

View File

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

View File

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

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

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

View File

@@ -385,4 +385,86 @@ class FooterActionsViewModelTest : SysuiTestCase() {
underTest.onVisibilityChangeRequested(visible = true)
assertThat(underTest.isVisible.value).isTrue()
}
@Test
fun alpha_inSplitShade_followsExpansion() {
val underTest = utils.footerActionsViewModel()
underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = true)
assertThat(underTest.alpha.value).isEqualTo(0f)
underTest.onQuickSettingsExpansionChanged(0.25f, isInSplitShade = true)
assertThat(underTest.alpha.value).isEqualTo(0.25f)
underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = true)
assertThat(underTest.alpha.value).isEqualTo(0.5f)
underTest.onQuickSettingsExpansionChanged(0.75f, isInSplitShade = true)
assertThat(underTest.alpha.value).isEqualTo(0.75f)
underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = true)
assertThat(underTest.alpha.value).isEqualTo(1f)
}
@Test
fun backgroundAlpha_inSplitShade_followsExpansion_with_0_99_delay() {
val underTest = utils.footerActionsViewModel()
val floatTolerance = 0.01f
underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = true)
assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = true)
assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
underTest.onQuickSettingsExpansionChanged(0.9f, isInSplitShade = true)
assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
underTest.onQuickSettingsExpansionChanged(0.991f, isInSplitShade = true)
assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.1f)
underTest.onQuickSettingsExpansionChanged(0.995f, isInSplitShade = true)
assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.5f)
underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = true)
assertThat(underTest.backgroundAlpha.value).isEqualTo(1f)
}
@Test
fun alpha_inSingleShade_followsExpansion_with_0_9_delay() {
val underTest = utils.footerActionsViewModel()
val floatTolerance = 0.01f
underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = false)
assertThat(underTest.alpha.value).isEqualTo(0f)
underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = false)
assertThat(underTest.alpha.value).isEqualTo(0f)
underTest.onQuickSettingsExpansionChanged(0.9f, isInSplitShade = false)
assertThat(underTest.alpha.value).isEqualTo(0f)
underTest.onQuickSettingsExpansionChanged(0.91f, isInSplitShade = false)
assertThat(underTest.alpha.value).isWithin(floatTolerance).of(0.1f)
underTest.onQuickSettingsExpansionChanged(0.95f, isInSplitShade = false)
assertThat(underTest.alpha.value).isWithin(floatTolerance).of(0.5f)
underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = false)
assertThat(underTest.alpha.value).isEqualTo(1f)
}
@Test
fun backgroundAlpha_inSingleShade_always1() {
val underTest = utils.footerActionsViewModel()
underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = false)
assertThat(underTest.backgroundAlpha.value).isEqualTo(1f)
underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = false)
assertThat(underTest.backgroundAlpha.value).isEqualTo(1f)
underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = false)
assertThat(underTest.backgroundAlpha.value).isEqualTo(1f)
}
}

View File

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