From d3c5c568a8b02926e5eee65a54cd928e9f78a49d Mon Sep 17 00:00:00 2001 From: Michal Brzezinski Date: Thu, 29 Jul 2021 13:43:02 +0200 Subject: [PATCH] Extracting buttons below expanded QS to a separate view and controller This is first part of showing utility bar (aka 3/4 buttons below expanded QS) in collapsed QS in split shade. This change doesn't modify any behaviour/layout, just extracts utility bar to a separate layout (qs_footer_actions), view (QSFooterActionsView) and controller (QSFooterActionsController). Next change will use this extracted entities to add utility bar to collapsed QS. Bug: 195098483 Test: QSFooterActionsControllerTest + QSFooterViewControllerTest + all tests from com.android.systemui.qs Change-Id: I5b49dd160bd777f12fd8696e56395830b2f81d30 --- .../res-keyguard/layout/qs_footer_actions.xml | 104 ++++++++++ .../SystemUI/res/layout/qs_footer_impl.xml | 87 +------- .../src/com/android/systemui/qs/QSFooter.java | 5 - .../systemui/qs/QSFooterActionsController.kt | 167 ++++++++++++++++ .../systemui/qs/QSFooterActionsView.kt | 159 +++++++++++++++ .../com/android/systemui/qs/QSFooterView.java | 126 ++---------- .../systemui/qs/QSFooterViewController.java | 188 ++---------------- .../systemui/qs/dagger/QSFragmentModule.java | 7 + .../qs/QSFooterActionsControllerTest.kt | 91 +++++++++ .../qs/QSFooterViewControllerTest.java | 91 +-------- 10 files changed, 571 insertions(+), 454 deletions(-) create mode 100644 packages/SystemUI/res-keyguard/layout/qs_footer_actions.xml create mode 100644 packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsController.kt create mode 100644 packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsView.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterActionsControllerTest.kt diff --git a/packages/SystemUI/res-keyguard/layout/qs_footer_actions.xml b/packages/SystemUI/res-keyguard/layout/qs_footer_actions.xml new file mode 100644 index 0000000000000..181ba078e948b --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/qs_footer_actions.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index 317dbc09eae61..fe0b14aaad8d2 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -68,93 +68,8 @@ - + - - - - - - - - - - - - - - - - - - diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java index e38bd4bd9a387..0e0681b94c62d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java @@ -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. * diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsController.kt new file mode 100644 index 0000000000000..dbf62a4361971 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsController.kt @@ -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(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 +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsView.kt new file mode 100644 index 0000000000000..66a29a36f351c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterActionsView.kt @@ -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) + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java index 57438d189b221..7db13bdb64bbe 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java @@ -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); - } -} +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java index 929aedae67067..c8ae5904e8e90 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java @@ -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 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 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 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 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); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java index 2046550413d3c..2de2d040f6e3e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterActionsControllerTest.kt new file mode 100644 index 0000000000000..9378b2be2945a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterActionsControllerTest.kt @@ -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(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(R.id.settings_button).performClick() + // Verify Settings wasn't launched. + verify(activityStarter, Mockito.never()).startActivity(any(), anyBoolean()) + } +} \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java index 6f7bf3b09daa8..8c6c358385ed9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java @@ -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 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 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); - } }