Merge "Extracting buttons below expanded QS to a separate view and controller" into sc-v2-dev

This commit is contained in:
Michał Brzeziński
2021-08-05 18:13:39 +00:00
committed by Android (Google) Code Review
10 changed files with 571 additions and 454 deletions

View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
** Copyright 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.
-->
<com.android.systemui.qs.QSFooterActionsView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/qs_footer_actions_container"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical">
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@android:id/edit"
android:layout_width="0dp"
android:layout_height="@dimen/qs_footer_action_button_size"
android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
android:layout_weight="1"
android:background="@drawable/qs_footer_action_chip_background"
android:clickable="true"
android:clipToPadding="false"
android:contentDescription="@string/accessibility_quick_settings_edit"
android:focusable="true"
android:padding="@dimen/qs_footer_icon_padding"
android:src="@*android:drawable/ic_mode_edit"
android:tint="?android:attr/textColorPrimary" />
<com.android.systemui.statusbar.phone.MultiUserSwitch
android:id="@+id/multi_user_switch"
android:layout_width="0dp"
android:layout_height="@dimen/qs_footer_action_button_size"
android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
android:layout_weight="1"
android:background="@drawable/qs_footer_action_chip_background"
android:focusable="true">
<ImageView
android:id="@+id/multi_user_avatar"
android:layout_width="@dimen/multi_user_avatar_expanded_size"
android:layout_height="@dimen/multi_user_avatar_expanded_size"
android:layout_gravity="center"
android:scaleType="centerInside" />
</com.android.systemui.statusbar.phone.MultiUserSwitch>
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@+id/pm_lite"
android:layout_width="0dp"
android:layout_height="@dimen/qs_footer_action_button_size"
android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
android:layout_weight="1"
android:background="@drawable/qs_footer_action_chip_background"
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="?android:attr/textColorPrimary" />
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/settings_button_container"
android:layout_width="0dp"
android:layout_height="@dimen/qs_footer_action_button_size"
android:background="@drawable/qs_footer_action_chip_background"
android:layout_weight="1"
android:clipChildren="false"
android:clipToPadding="false">
<com.android.systemui.statusbar.phone.SettingsButton
android:id="@+id/settings_button"
android:layout_width="match_parent"
android:layout_height="@dimen/qs_footer_action_button_size"
android:layout_gravity="center"
android:contentDescription="@string/accessibility_quick_settings_settings"
android:background="@drawable/qs_footer_action_chip_background_borderless"
android:padding="@dimen/qs_footer_icon_padding"
android:scaleType="centerInside"
android:src="@drawable/ic_settings"
android:tint="?android:attr/textColorPrimary" />
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@+id/tuner_icon"
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="@dimen/qs_footer_icon_padding"
android:src="@drawable/tuner"
android:tint="?android:attr/textColorTertiary"
android:visibility="invisible" />
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
</com.android.systemui.qs.QSFooterActionsView>

View File

@@ -68,93 +68,8 @@
</LinearLayout>
<LinearLayout
android:id="@+id/qs_footer_actions_container"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical">
<include layout="@layout/qs_footer_actions"/>
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@android:id/edit"
android:layout_width="0dp"
android:layout_height="@dimen/qs_footer_action_button_size"
android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
android:layout_weight="1"
android:background="@drawable/qs_footer_action_chip_background"
android:clickable="true"
android:clipToPadding="false"
android:contentDescription="@string/accessibility_quick_settings_edit"
android:focusable="true"
android:padding="@dimen/qs_footer_icon_padding"
android:src="@*android:drawable/ic_mode_edit"
android:tint="?android:attr/textColorPrimary" />
<com.android.systemui.statusbar.phone.MultiUserSwitch
android:id="@+id/multi_user_switch"
android:layout_width="0dp"
android:layout_height="@dimen/qs_footer_action_button_size"
android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
android:layout_weight="1"
android:background="@drawable/qs_footer_action_chip_background"
android:focusable="true">
<ImageView
android:id="@+id/multi_user_avatar"
android:layout_width="@dimen/multi_user_avatar_expanded_size"
android:layout_height="@dimen/multi_user_avatar_expanded_size"
android:layout_gravity="center"
android:scaleType="centerInside" />
</com.android.systemui.statusbar.phone.MultiUserSwitch>
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@+id/pm_lite"
android:layout_width="0dp"
android:layout_height="@dimen/qs_footer_action_button_size"
android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
android:layout_weight="1"
android:background="@drawable/qs_footer_action_chip_background"
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="?android:attr/textColorPrimary" />
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/settings_button_container"
android:layout_width="0dp"
android:layout_height="@dimen/qs_footer_action_button_size"
android:background="@drawable/qs_footer_action_chip_background"
android:layout_weight="1"
android:clipChildren="false"
android:clipToPadding="false">
<com.android.systemui.statusbar.phone.SettingsButton
android:id="@+id/settings_button"
android:layout_width="match_parent"
android:layout_height="@dimen/qs_footer_action_button_size"
android:layout_gravity="center"
android:contentDescription="@string/accessibility_quick_settings_settings"
android:background="@drawable/qs_footer_action_chip_background_borderless"
android:padding="@dimen/qs_footer_icon_padding"
android:scaleType="centerInside"
android:src="@drawable/ic_settings"
android:tint="?android:attr/textColorPrimary" />
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@+id/tuner_icon"
android:layout_width="8dp"
android:layout_height="8dp"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="@dimen/qs_footer_icon_padding"
android:src="@drawable/tuner"
android:tint="?android:attr/textColorTertiary"
android:visibility="invisible" />
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
</LinearLayout>
</LinearLayout>
</com.android.systemui.qs.QSFooterView>

View File

@@ -34,11 +34,6 @@ public interface QSFooter {
*/
void setExpanded(boolean expanded);
/**
* Returns the full height of the footer.
*/
int getHeight();
/**
* Sets the percentage amount that the quick settings has been expanded.
*

View File

@@ -0,0 +1,167 @@
package com.android.systemui.qs
import android.content.Intent
import android.os.UserManager
import android.provider.Settings
import android.view.View
import android.widget.Toast
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.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.statusbar.phone.MultiUserSwitchController
import com.android.systemui.statusbar.phone.SettingsButton
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.tuner.TunerService
import com.android.systemui.util.ViewController
import javax.inject.Inject
import javax.inject.Named
class QSFooterActionsController @Inject constructor(
view: QSFooterActionsView,
private val qsPanelController: QSPanelController,
private val activityStarter: ActivityStarter,
private val userManager: UserManager,
private val userInfoController: UserInfoController,
private val multiUserSwitchController: MultiUserSwitchController,
private val deviceProvisionedController: DeviceProvisionedController,
private val falsingManager: FalsingManager,
private val metricsLogger: MetricsLogger,
private val tunerService: TunerService,
private val globalActionsDialog: GlobalActionsDialogLite,
private val uiEventLogger: UiEventLogger,
@Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean
) : ViewController<QSFooterActionsView>(view) {
private var listening: Boolean = false
var expanded = false
set(value) {
field = value
mView.setExpanded(value, isTunerEnabled(),
multiUserSwitchController.isMultiUserEnabled)
}
private val settingsButton: SettingsButton = view.findViewById(R.id.settings_button)
private val settingsButtonContainer: View? = view.findViewById(R.id.settings_button_container)
private val editButton: View = view.findViewById(android.R.id.edit)
private val powerMenuLite: View = view.findViewById(R.id.pm_lite)
private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ ->
val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
mView.onUserInfoChanged(picture, isGuestUser)
}
private val onClickListener = View.OnClickListener { v ->
// Don't do anything until views are unhidden. Don't do anything if the tap looks
// suspicious.
if (!expanded || falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return@OnClickListener
}
if (v === settingsButton) {
if (!deviceProvisionedController.isCurrentUserSetup) {
// If user isn't setup just unlock the device and dump them back at SUW.
activityStarter.postQSRunnableDismissingKeyguard {}
return@OnClickListener
}
metricsLogger.action(
if (expanded) MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
else MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH)
if (settingsButton.isTunerClick) {
activityStarter.postQSRunnableDismissingKeyguard {
if (isTunerEnabled()) {
tunerService.showResetRequest {
// Relaunch settings so that the tuner disappears.
startSettingsActivity()
}
} else {
Toast.makeText(context, R.string.tuner_toast, Toast.LENGTH_LONG).show()
tunerService.isTunerEnabled = true
}
startSettingsActivity()
}
} else {
startSettingsActivity()
}
} else if (v === powerMenuLite) {
uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
globalActionsDialog.showOrHideDialog(false, true)
}
}
override fun onInit() {
multiUserSwitchController.init()
}
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() {
if (showPMLiteButton) {
powerMenuLite.visibility = View.VISIBLE
powerMenuLite.setOnClickListener(onClickListener)
} else {
powerMenuLite.visibility = View.GONE
}
settingsButton.setOnClickListener(onClickListener)
editButton.setOnClickListener(View.OnClickListener { view: View? ->
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return@OnClickListener
}
activityStarter.postQSRunnableDismissingKeyguard { qsPanelController.showEdit(view) }
})
mView.updateEverything(isTunerEnabled(), multiUserSwitchController.isMultiUserEnabled)
}
override fun onViewDetached() {
setListening(false)
}
fun setListening(listening: Boolean) {
if (this.listening == listening) {
return
}
this.listening = listening
if (this.listening) {
userInfoController.addCallback(onUserInfoChangedListener)
} else {
userInfoController.removeCallback(onUserInfoChangedListener)
}
}
fun disable(state2: Int) {
mView.disable(state2, isTunerEnabled(), multiUserSwitchController.isMultiUserEnabled)
}
fun setExpansion(headerExpansionFraction: Float) {
mView.setExpansion(headerExpansionFraction)
}
fun updateAnimator(width: Int, numTiles: Int) {
mView.updateAnimator(width, numTiles)
}
fun setKeyguardShowing() {
mView.setKeyguardShowing()
}
private fun isTunerEnabled() = tunerService.isTunerEnabled
}

View File

@@ -0,0 +1,159 @@
/*
* 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.content.res.Configuration
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
import android.graphics.drawable.RippleDrawable
import android.os.UserManager
import android.util.AttributeSet
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.settingslib.Utils
import com.android.settingslib.drawable.UserIconDrawable
import com.android.systemui.R
import com.android.systemui.statusbar.phone.MultiUserSwitch
import com.android.systemui.statusbar.phone.SettingsButton
/**
* 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
*/
class QSFooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) {
private lateinit var settingsContainer: View
private lateinit var settingsButton: SettingsButton
private lateinit var multiUserSwitch: MultiUserSwitch
private lateinit var multiUserAvatar: ImageView
private lateinit var tunerIcon: View
private lateinit var editTilesButton: View
private var settingsCogAnimator: TouchAnimator? = null
private var qsDisabled = false
private var isExpanded = false
private var expansionAmount = 0f
override fun onFinishInflate() {
super.onFinishInflate()
editTilesButton = requireViewById(android.R.id.edit)
settingsButton = findViewById(R.id.settings_button)
settingsContainer = findViewById(R.id.settings_button_container)
multiUserSwitch = findViewById(R.id.multi_user_switch)
multiUserAvatar = multiUserSwitch.findViewById(R.id.multi_user_avatar)
tunerIcon = requireViewById(R.id.tuner_icon)
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
if (settingsButton.background is RippleDrawable) {
(settingsButton.background as RippleDrawable).setForceSoftware(true)
}
updateResources()
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
}
fun updateAnimator(width: Int, numTiles: Int) {
val size = (mContext.resources.getDimensionPixelSize(R.dimen.qs_quick_tile_size) -
mContext.resources.getDimensionPixelSize(R.dimen.qs_tile_padding))
val remaining = (width - numTiles * size) / (numTiles - 1)
val defSpace = mContext.resources.getDimensionPixelOffset(R.dimen.default_gear_space)
val translation = if (isLayoutRtl) (remaining - defSpace) else -(remaining - defSpace)
settingsCogAnimator = TouchAnimator.Builder()
.addFloat(settingsButton, "translationX", translation.toFloat(), 0f)
.addFloat(settingsButton, "rotation", -120f, 0f)
.build()
setExpansion(expansionAmount)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
updateResources()
}
override fun onRtlPropertiesChanged(layoutDirection: Int) {
super.onRtlPropertiesChanged(layoutDirection)
updateResources()
}
private fun updateResources() {
val tunerIconTranslation = mContext.resources
.getDimensionPixelOffset(R.dimen.qs_footer_tuner_icon_translation).toFloat()
tunerIcon.translationX = if (isLayoutRtl) (-tunerIconTranslation) else tunerIconTranslation
}
fun setKeyguardShowing() {
setExpansion(expansionAmount)
}
fun setExpanded(expanded: Boolean, isTunerEnabled: Boolean, multiUserEnabled: Boolean) {
if (isExpanded == expanded) return
isExpanded = expanded
updateEverything(isTunerEnabled, multiUserEnabled)
}
fun setExpansion(headerExpansionFraction: Float) {
expansionAmount = headerExpansionFraction
if (settingsCogAnimator != null) settingsCogAnimator!!.setPosition(headerExpansionFraction)
}
fun disable(state2: Int, isTunerEnabled: Boolean, multiUserEnabled: Boolean) {
val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
if (disabled == qsDisabled) return
qsDisabled = disabled
updateEverything(isTunerEnabled, multiUserEnabled)
}
fun updateEverything(isTunerEnabled: Boolean, multiUserEnabled: Boolean) {
post {
updateVisibilities(isTunerEnabled, multiUserEnabled)
updateClickabilities()
isClickable = false
}
}
private fun updateClickabilities() {
multiUserSwitch.isClickable = multiUserSwitch.visibility == VISIBLE
editTilesButton.isClickable = editTilesButton.visibility == VISIBLE
settingsButton.isClickable = settingsButton.visibility == VISIBLE
}
private fun updateVisibilities(isTunerEnabled: Boolean, multiUserEnabled: Boolean) {
settingsContainer.visibility = if (qsDisabled) GONE else VISIBLE
tunerIcon.visibility = if (isTunerEnabled) VISIBLE else INVISIBLE
multiUserSwitch.visibility = if (showUserSwitcher(multiUserEnabled)) VISIBLE else GONE
val isDemo = UserManager.isDeviceInDemoMode(context)
settingsButton.visibility = if (isDemo || !isExpanded) INVISIBLE else VISIBLE
}
private fun showUserSwitcher(multiUserEnabled: Boolean): Boolean {
return isExpanded && multiUserEnabled
}
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)
}
}

View File

@@ -21,60 +21,40 @@ import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
import android.content.Context;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.graphics.PorterDuff.Mode;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.AttributeSet;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.settingslib.Utils;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
import com.android.settingslib.drawable.UserIconDrawable;
import com.android.systemui.R;
import com.android.systemui.qs.TouchAnimator.Builder;
import com.android.systemui.statusbar.phone.MultiUserSwitch;
import com.android.systemui.statusbar.phone.SettingsButton;
/** */
/**
* Footer of expanded Quick Settings, tiles page indicator, (optionally) build number and
* {@link QSFooterActionsView}
*/
public class QSFooterView extends FrameLayout {
private SettingsButton mSettingsButton;
protected View mSettingsContainer;
private PageIndicator mPageIndicator;
private TextView mBuildText;
private boolean mShouldShowBuildText;
private boolean mQsDisabled;
private boolean mExpanded;
private boolean mListening;
protected MultiUserSwitch mMultiUserSwitch;
private ImageView mMultiUserAvatar;
private View mActionsContainer;
protected TouchAnimator mFooterAnimator;
private boolean mQsDisabled;
private boolean mExpanded;
private float mExpansionAmount;
protected View mEdit;
private TouchAnimator mSettingsCogAnimator;
private View mActionsContainer;
private View mTunerIcon;
private int mTunerIconTranslation;
private boolean mShouldShowBuildText;
private OnClickListener mExpandClickListener;
@@ -94,27 +74,11 @@ public class QSFooterView extends FrameLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mEdit = requireViewById(android.R.id.edit);
mPageIndicator = findViewById(R.id.footer_page_indicator);
mSettingsButton = findViewById(R.id.settings_button);
mSettingsContainer = findViewById(R.id.settings_button_container);
mMultiUserSwitch = findViewById(R.id.multi_user_switch);
mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
mActionsContainer = requireViewById(R.id.qs_footer_actions_container);
mBuildText = findViewById(R.id.build);
mTunerIcon = requireViewById(R.id.tuner_icon);
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
if (mSettingsButton.getBackground() instanceof RippleDrawable) {
((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true);
}
updateResources();
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
setBuildText();
}
@@ -137,18 +101,7 @@ public class QSFooterView extends FrameLayout {
}
}
void updateAnimator(int width, int numTiles) {
int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size)
- mContext.getResources().getDimensionPixelSize(R.dimen.qs_tile_padding);
int remaining = (width - numTiles * size) / (numTiles - 1);
int defSpace = mContext.getResources().getDimensionPixelOffset(R.dimen.default_gear_space);
mSettingsCogAnimator = new Builder()
.addFloat(mSettingsButton, "translationX",
isLayoutRtl() ? (remaining - defSpace) : -(remaining - defSpace), 0)
.addFloat(mSettingsButton, "rotation", -120, 0)
.build();
void updateExpansion() {
setExpansion(mExpansionAmount);
}
@@ -158,20 +111,11 @@ public class QSFooterView extends FrameLayout {
updateResources();
}
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
super.onRtlPropertiesChanged(layoutDirection);
updateResources();
}
private void updateResources() {
updateFooterAnimator();
MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
lp.bottomMargin = getResources().getDimensionPixelSize(R.dimen.qs_footers_margin_bottom);
setLayoutParams(lp);
mTunerIconTranslation = mContext.getResources()
.getDimensionPixelOffset(R.dimen.qs_footer_tuner_icon_translation);
mTunerIcon.setTranslationX(isLayoutRtl() ? -mTunerIconTranslation : mTunerIconTranslation);
}
private void updateFooterAnimator() {
@@ -197,17 +141,15 @@ public class QSFooterView extends FrameLayout {
mExpandClickListener = onClickListener;
}
void setExpanded(boolean expanded, boolean isTunerEnabled, boolean multiUserEnabled) {
void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
mExpanded = expanded;
updateEverything(isTunerEnabled, multiUserEnabled);
updateEverything();
}
/** */
public void setExpansion(float headerExpansionFraction) {
mExpansionAmount = headerExpansionFraction;
if (mSettingsCogAnimator != null) mSettingsCogAnimator.setPosition(headerExpansionFraction);
if (mFooterAnimator != null) {
mFooterAnimator.setPosition(headerExpansionFraction);
}
@@ -228,14 +170,6 @@ public class QSFooterView extends FrameLayout {
super.onDetachedFromWindow();
}
/** */
public void setListening(boolean listening) {
if (listening == mListening) {
return;
}
mListening = listening;
}
@Override
public boolean performAccessibilityAction(int action, Bundle arguments) {
if (action == AccessibilityNodeInfo.ACTION_EXPAND) {
@@ -253,50 +187,26 @@ public class QSFooterView extends FrameLayout {
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
}
void disable(int state2, boolean isTunerEnabled, boolean multiUserEnabled) {
void disable(int state2) {
final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
if (disabled == mQsDisabled) return;
mQsDisabled = disabled;
updateEverything(isTunerEnabled, multiUserEnabled);
updateEverything();
}
void updateEverything(boolean isTunerEnabled, boolean multiUserEnabled) {
void updateEverything() {
post(() -> {
updateVisibilities(isTunerEnabled, multiUserEnabled);
updateVisibilities();
updateClickabilities();
setClickable(false);
});
}
private void updateClickabilities() {
mMultiUserSwitch.setClickable(mMultiUserSwitch.getVisibility() == View.VISIBLE);
mEdit.setClickable(mEdit.getVisibility() == View.VISIBLE);
mSettingsButton.setClickable(mSettingsButton.getVisibility() == View.VISIBLE);
mBuildText.setLongClickable(mBuildText.getVisibility() == View.VISIBLE);
}
private void updateVisibilities(boolean isTunerEnabled, boolean multiUserEnabled) {
mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
mTunerIcon.setVisibility(isTunerEnabled ? View.VISIBLE : View.INVISIBLE);
final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
mMultiUserSwitch.setVisibility(
showUserSwitcher(multiUserEnabled) ? View.VISIBLE : View.GONE);
mSettingsButton.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
private void updateVisibilities() {
mBuildText.setVisibility(mExpanded && mShouldShowBuildText ? View.VISIBLE : View.INVISIBLE);
}
private boolean showUserSwitcher(boolean multiUserEnabled) {
return mExpanded && multiUserEnabled;
}
void onUserInfoChanged(Drawable picture, boolean isGuestUser) {
if (picture != null && isGuestUser && !(picture instanceof UserIconDrawable)) {
picture = picture.getConstantState().newDrawable(getResources()).mutate();
picture.setColorFilter(
Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorForeground),
Mode.SRC_IN);
}
mMultiUserAvatar.setImageDrawable(picture);
}
}
}

View File

@@ -16,39 +16,19 @@
package com.android.systemui.qs;
import static com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.UserManager;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
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.globalactions.GlobalActionsDialogLite;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
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.phone.SettingsButton;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
import javax.inject.Named;
/**
* Controller for {@link QSFooterView}.
@@ -56,137 +36,45 @@ import javax.inject.Named;
@QSScope
public class QSFooterViewController extends ViewController<QSFooterView> implements QSFooter {
private final UserManager mUserManager;
private final UserInfoController mUserInfoController;
private final ActivityStarter mActivityStarter;
private final DeviceProvisionedController mDeviceProvisionedController;
private final UserTracker mUserTracker;
private final QSPanelController mQsPanelController;
private final QuickQSPanelController mQuickQSPanelController;
private final TunerService mTunerService;
private final MetricsLogger mMetricsLogger;
private final FalsingManager mFalsingManager;
private final MultiUserSwitchController mMultiUserSwitchController;
private final SettingsButton mSettingsButton;
private final View mSettingsButtonContainer;
private final QSFooterActionsController mQsFooterActionsController;
private final TextView mBuildText;
private final View mEdit;
private final PageIndicator mPageIndicator;
private final View mPowerMenuLite;
private final boolean mShowPMLiteButton;
private final GlobalActionsDialogLite mGlobalActionsDialog;
private final UiEventLogger mUiEventLogger;
private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener =
new UserInfoController.OnUserInfoChangedListener() {
@Override
public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
boolean isGuestUser = mUserManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser());
mView.onUserInfoChanged(picture, isGuestUser);
}
};
private final View.OnClickListener mSettingsOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
// Don't do anything until views are unhidden. Don't do anything if the tap looks
// suspicious.
if (!mExpanded || mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
}
if (v == mSettingsButton) {
if (!mDeviceProvisionedController.isCurrentUserSetup()) {
// If user isn't setup just unlock the device and dump them back at SUW.
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
});
return;
}
mMetricsLogger.action(
mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
: MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
if (mSettingsButton.isTunerClick()) {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
if (isTunerEnabled()) {
mTunerService.showResetRequest(
() -> {
// Relaunch settings so that the tuner disappears.
startSettingsActivity();
});
} else {
Toast.makeText(getContext(), R.string.tuner_toast,
Toast.LENGTH_LONG).show();
mTunerService.setTunerEnabled(true);
}
startSettingsActivity();
});
} else {
startSettingsActivity();
}
} else if (v == mPowerMenuLite) {
mUiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS);
mGlobalActionsDialog.showOrHideDialog(false, true);
}
}
};
private boolean mListening;
private boolean mExpanded;
@Inject
QSFooterViewController(QSFooterView view, UserManager userManager,
UserInfoController userInfoController, ActivityStarter activityStarter,
DeviceProvisionedController deviceProvisionedController, UserTracker userTracker,
QSFooterViewController(QSFooterView view,
UserTracker userTracker,
QSPanelController qsPanelController,
MultiUserSwitchController multiUserSwitchController,
QuickQSPanelController quickQSPanelController,
TunerService tunerService, MetricsLogger metricsLogger, FalsingManager falsingManager,
@Named(PM_LITE_ENABLED) boolean showPMLiteButton,
GlobalActionsDialogLite globalActionsDialog, UiEventLogger uiEventLogger) {
QSFooterActionsController qsFooterActionsController) {
super(view);
mUserManager = userManager;
mUserInfoController = userInfoController;
mActivityStarter = activityStarter;
mDeviceProvisionedController = deviceProvisionedController;
mUserTracker = userTracker;
mQsPanelController = qsPanelController;
mQuickQSPanelController = quickQSPanelController;
mTunerService = tunerService;
mMetricsLogger = metricsLogger;
mFalsingManager = falsingManager;
mMultiUserSwitchController = multiUserSwitchController;
mQsFooterActionsController = qsFooterActionsController;
mSettingsButton = mView.findViewById(R.id.settings_button);
mSettingsButtonContainer = mView.findViewById(R.id.settings_button_container);
mBuildText = mView.findViewById(R.id.build);
mEdit = mView.findViewById(android.R.id.edit);
mPageIndicator = mView.findViewById(R.id.footer_page_indicator);
mPowerMenuLite = mView.findViewById(R.id.pm_lite);
mShowPMLiteButton = showPMLiteButton;
mGlobalActionsDialog = globalActionsDialog;
mUiEventLogger = uiEventLogger;
}
@Override
protected void onInit() {
super.onInit();
mMultiUserSwitchController.init();
mQsFooterActionsController.init();
}
@Override
protected void onViewAttached() {
if (mShowPMLiteButton) {
mPowerMenuLite.setVisibility(View.VISIBLE);
mPowerMenuLite.setOnClickListener(mSettingsOnClickListener);
} else {
mPowerMenuLite.setVisibility(View.GONE);
}
mView.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
mView.updateAnimator(
right - left, mQuickQSPanelController.getNumQuickTiles()));
mSettingsButton.setOnClickListener(mSettingsOnClickListener);
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
mView.updateExpansion();
mQsFooterActionsController.updateAnimator(right - left,
mQuickQSPanelController.getNumQuickTiles());
}
);
mBuildText.setOnLongClickListener(view -> {
CharSequence buildText = mBuildText.getText();
if (!TextUtils.isEmpty(buildText)) {
@@ -200,17 +88,8 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme
}
return false;
});
mEdit.setOnClickListener(view -> {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
}
mActivityStarter.postQSRunnableDismissingKeyguard(() ->
mQsPanelController.showEdit(view));
});
mQsPanelController.setFooterPageIndicator(mPageIndicator);
mView.updateEverything(isTunerEnabled(), mMultiUserSwitchController.isMultiUserEnabled());
mView.updateEverything();
}
@Override
@@ -225,38 +104,25 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme
@Override
public void setExpanded(boolean expanded) {
mExpanded = expanded;
mView.setExpanded(
expanded, isTunerEnabled(), mMultiUserSwitchController.isMultiUserEnabled());
}
@Override
public int getHeight() {
return mView.getHeight();
mQsFooterActionsController.setExpanded(expanded);
mView.setExpanded(expanded);
}
@Override
public void setExpansion(float expansion) {
mView.setExpansion(expansion);
mQsFooterActionsController.setExpansion(expansion);
}
@Override
public void setListening(boolean listening) {
if (mListening == listening) {
return;
}
mListening = listening;
if (mListening) {
mUserInfoController.addCallback(mOnUserInfoChangedListener);
} else {
mUserInfoController.removeCallback(mOnUserInfoChangedListener);
}
mQsFooterActionsController.setListening(listening);
}
@Override
public void setKeyguardShowing(boolean keyguardShowing) {
mView.setKeyguardShowing();
mQsFooterActionsController.setKeyguardShowing();
}
/** */
@@ -267,19 +133,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme
@Override
public void disable(int state1, int state2, boolean animate) {
mView.disable(state2, isTunerEnabled(), mMultiUserSwitchController.isMultiUserEnabled());
}
private void startSettingsActivity() {
ActivityLaunchAnimator.Controller animationController =
mSettingsButtonContainer != null ? ActivityLaunchAnimator.Controller.fromView(
mSettingsButtonContainer,
InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON) : null;
mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
true /* dismissShade */, animationController);
}
private boolean isTunerEnabled() {
return mTunerService.isTunerEnabled();
mView.disable(state2);
mQsFooterActionsController.disable(state2);
}
}

View File

@@ -28,6 +28,7 @@ import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.QSContainerImpl;
import com.android.systemui.qs.QSFooter;
import com.android.systemui.qs.QSFooterActionsView;
import com.android.systemui.qs.QSFooterView;
import com.android.systemui.qs.QSFooterViewController;
import com.android.systemui.qs.QSFragment;
@@ -120,6 +121,12 @@ public interface QSFragmentModule {
return view.findViewById(R.id.qs_footer);
}
/** */
@Provides
static QSFooterActionsView providesQSFooterActionsView(@RootView View view) {
return view.findViewById(R.id.qs_footer_actions_container);
}
/** */
@Provides
@QSScope

View File

@@ -0,0 +1,91 @@
package com.android.systemui.qs
import com.android.systemui.R
import android.os.UserManager
import android.view.LayoutInflater
import android.view.View
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.Dependency
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.MultiUserSwitchController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.tuner.TunerService
import com.android.systemui.utils.leaks.FakeTunerService
import com.android.systemui.utils.leaks.LeakCheckedTest
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
@SmallTest
class QSFooterActionsControllerTest : LeakCheckedTest() {
@Mock
private lateinit var userManager: UserManager
@Mock
private lateinit var activityStarter: ActivityStarter
@Mock
private lateinit var deviceProvisionedController: DeviceProvisionedController
@Mock
private lateinit var userInfoController: UserInfoController
@Mock
private lateinit var qsPanelController: QSPanelController
@Mock
private lateinit var multiUserSwitchController: MultiUserSwitchController
@Mock
private lateinit var globalActionsDialog: GlobalActionsDialogLite
@Mock
private lateinit var uiEventLogger: UiEventLogger
@Mock
private lateinit var controller: QSFooterActionsController
private val metricsLogger: MetricsLogger = FakeMetricsLogger()
private lateinit var view: QSFooterActionsView
private val falsingManager: FalsingManagerFake = FalsingManagerFake()
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
injectLeakCheckedDependencies(*LeakCheckedTest.ALL_SUPPORTED_CLASSES)
val fakeTunerService = Dependency.get(TunerService::class.java) as FakeTunerService
view = LayoutInflater.from(context)
.inflate(R.layout.qs_footer_actions, null) as QSFooterActionsView
controller = QSFooterActionsController(view, qsPanelController, activityStarter,
userManager, userInfoController, multiUserSwitchController,
deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService,
globalActionsDialog, uiEventLogger, showPMLiteButton = true)
controller.init()
controller.onViewAttached()
}
@Test
fun testLogPowerMenuClick() {
controller.expanded = 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_UserNotSetup() {
whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
view.findViewById<View>(R.id.settings_button).performClick()
// Verify Settings wasn't launched.
verify<ActivityStarter>(activityStarter, Mockito.never()).startActivity(any(), anyBoolean())
}
}

View File

@@ -18,16 +18,11 @@ package com.android.systemui.qs;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
@@ -35,21 +30,8 @@ import android.widget.TextView;
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.Dependency;
import com.android.systemui.R;
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.phone.SettingsButton;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.utils.leaks.FakeTunerService;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.Before;
@@ -67,14 +49,6 @@ public class QSFooterViewControllerTest extends LeakCheckedTest {
@Mock
private QSFooterView mView;
@Mock
private UserManager mUserManager;
@Mock
private ActivityStarter mActivityStarter;
@Mock
private DeviceProvisionedController mDeviceProvisionedController;
@Mock
private UserInfoController mUserInfoController;
@Mock
private UserTracker mUserTracker;
@Mock
private QSPanelController mQSPanelController;
@@ -82,36 +56,19 @@ public class QSFooterViewControllerTest extends LeakCheckedTest {
private ClipboardManager mClipboardManager;
@Mock
private QuickQSPanelController mQuickQSPanelController;
private FakeTunerService mFakeTunerService;
private MetricsLogger mMetricsLogger = new FakeMetricsLogger();
private FalsingManagerFake mFalsingManager;
@Mock
private SettingsButton mSettingsButton;
@Mock
private TextView mBuildText;
@Mock
private View mEdit;
@Mock
private MultiUserSwitchController mMultiUserSwitchController;
@Mock
private View mPowerMenuLiteView;
@Mock
private GlobalActionsDialogLite mGlobalActionsDialog;
@Mock
private UiEventLogger mUiEventLogger;
private QSFooterActionsController mQSFooterActionsController;
private QSFooterViewController mController;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
mFalsingManager = new FalsingManagerFake();
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
mFakeTunerService = (FakeTunerService) Dependency.get(TunerService.class);
mContext.addMockSystemService(ClipboardManager.class, mClipboardManager);
when(mView.getContext()).thenReturn(mContext);
@@ -119,16 +76,10 @@ public class QSFooterViewControllerTest extends LeakCheckedTest {
when(mUserTracker.getUserContext()).thenReturn(mContext);
when(mView.isAttachedToWindow()).thenReturn(true);
when(mView.findViewById(R.id.settings_button)).thenReturn(mSettingsButton);
when(mView.findViewById(R.id.build)).thenReturn(mBuildText);
when(mView.findViewById(android.R.id.edit)).thenReturn(mEdit);
when(mView.findViewById(R.id.pm_lite)).thenReturn(mPowerMenuLiteView);
mController = new QSFooterViewController(mView, mUserManager, mUserInfoController,
mActivityStarter, mDeviceProvisionedController, mUserTracker, mQSPanelController,
mMultiUserSwitchController, mQuickQSPanelController, mFakeTunerService,
mMetricsLogger, mFalsingManager, false, mGlobalActionsDialog,
mUiEventLogger);
mController = new QSFooterViewController(mView, mUserTracker, mQSPanelController,
mQuickQSPanelController, mQSFooterActionsController);
mController.init();
}
@@ -148,40 +99,4 @@ public class QSFooterViewControllerTest extends LeakCheckedTest {
verify(mClipboardManager).setPrimaryClip(captor.capture());
assertThat(captor.getValue().getItemAt(0).getText()).isEqualTo(text);
}
@Test
public void testSettings_UserNotSetup() {
ArgumentCaptor<View.OnClickListener> onClickCaptor =
ArgumentCaptor.forClass(View.OnClickListener.class);
verify(mSettingsButton).setOnClickListener(onClickCaptor.capture());
when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(false);
onClickCaptor.getValue().onClick(mSettingsButton);
// Verify Settings wasn't launched.
verify(mActivityStarter, never()).startActivity(any(), anyBoolean());
}
@Test
public void testLogPowerMenuClick() {
// Enable power menu button
mController = new QSFooterViewController(mView, mUserManager, mUserInfoController,
mActivityStarter, mDeviceProvisionedController, mUserTracker, mQSPanelController,
mMultiUserSwitchController, mQuickQSPanelController, mFakeTunerService,
mMetricsLogger, new FalsingManagerFake(), true, mGlobalActionsDialog,
mUiEventLogger);
mController.init();
mController.setExpanded(true);
mFalsingManager.setFalseTap(false);
ArgumentCaptor<View.OnClickListener> onClickCaptor =
ArgumentCaptor.forClass(View.OnClickListener.class);
verify(mPowerMenuLiteView).setOnClickListener(onClickCaptor.capture());
onClickCaptor.getValue().onClick(mPowerMenuLiteView);
// Verify clicks are logged
verify(mUiEventLogger, times(1))
.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS);
}
}