From d68c626be06be2770ff52dc0a2473f1d9b90f74f Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Mon, 7 Feb 2022 13:04:09 -0500 Subject: [PATCH] Put security footers in new footer This CL puts the security footer and fgs disclaimer in the new footer (based on flag) instead of QSPanel. The FGS disclaimer has two modes. A text mode (when security footer is not visible) or just a number with maybe an update dot (when security footer is visible). Also, use DialogLaunchAnimator for QSSecurityFooter Test: manual Test: atest SystemUITests Fixes: 217908230 Change-Id: Id9289826de3256ea6fdfea3ca6f8bf1976364e61 --- .../res-keyguard/layout/fgs_footer.xml | 95 +++++++++++++++++++ .../layout/new_footer_actions.xml | 8 +- .../SystemUI/res/drawable/new_fgs_dot.xml | 22 +++++ .../qs_security_footer_background.xml | 4 +- packages/SystemUI/res/values/strings.xml | 3 + .../systemui/qs/FgsManagerController.kt | 7 ++ .../systemui/qs/FooterActionsController.kt | 54 ++++++++++- .../systemui/qs/QSFgsManagerFooter.java | 54 ++++++++++- .../systemui/qs/QSPanelController.java | 15 ++- .../android/systemui/qs/QSSecurityFooter.java | 82 ++++++++++------ .../qs/VisibilityChangedDispatcher.kt | 28 ++++++ .../systemui/qs/dagger/QSFragmentModule.java | 2 +- .../util/DualHeightHorizontalLinearLayout.kt | 17 +++- .../qs/FooterActionsControllerTest.kt | 7 +- .../systemui/qs/QSSecurityFooterTest.java | 79 ++++++++++++--- 15 files changed, 416 insertions(+), 61 deletions(-) create mode 100644 packages/SystemUI/res-keyguard/layout/fgs_footer.xml create mode 100644 packages/SystemUI/res/drawable/new_fgs_dot.xml create mode 100644 packages/SystemUI/src/com/android/systemui/qs/VisibilityChangedDispatcher.kt diff --git a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml new file mode 100644 index 0000000000000..5343411e4c958 --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml index 175e45c12afa8..4884df7f838e7 100644 --- a/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml +++ b/packages/SystemUI/res-keyguard/layout/new_footer_actions.xml @@ -29,11 +29,13 @@ android:layout_gravity="bottom" > - + /> + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_security_footer_background.xml b/packages/SystemUI/res/drawable/qs_security_footer_background.xml index 860d23b114391..381af503d47c0 100644 --- a/packages/SystemUI/res/drawable/qs_security_footer_background.xml +++ b/packages/SystemUI/res/drawable/qs_security_footer_background.xml @@ -15,8 +15,8 @@ ~ limitations under the License. --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index e5cabb0ecac08..bdc9dbd8f6c86 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2375,6 +2375,9 @@ %s active app %s active apps + + New information Active apps diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt index eb3415639db60..d26c1c54ecb8d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt @@ -40,6 +40,7 @@ import androidx.recyclerview.widget.RecyclerView import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED import com.android.systemui.R import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.statusbar.phone.SystemUIDialog @@ -50,6 +51,7 @@ import java.util.concurrent.Executor import javax.inject.Inject import kotlin.math.max +@SysUISingleton class FgsManagerController @Inject constructor( private val context: Context, @Main private val mainExecutor: Executor, @@ -65,6 +67,9 @@ class FgsManagerController @Inject constructor( private val LOG_TAG = FgsManagerController::class.java.simpleName } + var changesSinceDialog = false + private set + private var isAvailable = false private val lock = Any() @@ -137,6 +142,7 @@ class FgsManagerController @Inject constructor( val numPackagesAfter = getNumRunningPackagesLocked() if (numPackagesAfter != numPackagesBefore) { + changesSinceDialog = true onNumberOfPackagesChangedListeners.forEach { backgroundExecutor.execute { it.onNumberOfPackagesChanged(numPackagesAfter) } } @@ -210,6 +216,7 @@ class FgsManagerController @Inject constructor( this.dialog = dialog dialog.setOnDismissListener { + changesSinceDialog = false synchronized(lock) { this.dialog = null updateAppItemsLocked() diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt index 92e3c3cc4106e..77feb90f575a8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt @@ -22,6 +22,8 @@ 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 android.widget.LinearLayout import android.widget.Toast import androidx.annotation.VisibleForTesting import com.android.internal.jank.InteractionJankMonitor @@ -45,6 +47,7 @@ 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.DualHeightHorizontalLinearLayout import com.android.systemui.util.ViewController import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject @@ -56,7 +59,7 @@ import javax.inject.Named * determined by [buttonsVisibleState] */ @QSScope -class FooterActionsController @Inject constructor( +internal class FooterActionsController @Inject constructor( view: FooterActionsView, multiUserSwitchControllerFactory: MultiUserSwitchController.Factory, private val activityStarter: ActivityStarter, @@ -64,6 +67,8 @@ class FooterActionsController @Inject constructor( 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 tunerService: TunerService, @@ -91,8 +96,13 @@ class FooterActionsController @Inject constructor( private val settingsButton: SettingsButton = view.findViewById(R.id.settings_button) 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) + private val securityFootersSeparator = View(context).apply { + visibility = View.GONE + } private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ -> val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser()) @@ -145,6 +155,7 @@ class FooterActionsController @Inject constructor( override fun onInit() { multiUserSwitchController.init() + fgsManagerFooterController.init() } private fun updateVisibility() { @@ -172,9 +183,46 @@ class FooterActionsController @Inject constructor( powerMenuLite.visibility = View.GONE } settingsButton.setOnClickListener(onClickListener) + if (featureFlags.isEnabled(Flags.NEW_FOOTER)) { + val securityFooter = securityFooterController.view as DualHeightHorizontalLinearLayout + securityFootersContainer?.addView(securityFooter) + val separatorWidth = resources.getDimensionPixelSize(R.dimen.new_qs_footer_action_inset) + securityFootersContainer?.addView(securityFootersSeparator, separatorWidth, 1) + reformatForNewFooter(securityFooter) + val fgsFooter = fgsManagerFooterController.view + securityFootersContainer?.addView(fgsFooter) + + val visibilityListener = + VisibilityChangedDispatcher.OnVisibilityChangedListener { visibility -> + if (visibility == View.GONE) { + securityFootersSeparator.visibility = View.GONE + } else 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) + } updateView() } + private fun reformatForNewFooter(view: DualHeightHorizontalLinearLayout) { + // This is only necessary while things are flagged as the view could be attached in two + // different locations. + (view.layoutParams as LinearLayout.LayoutParams).apply { + bottomMargin = 0 + width = 0 + weight = 1f + marginEnd = resources.getDimensionPixelSize(R.dimen.new_qs_footer_action_inset) + } + view.alwaysSingleLine = true + } + private fun updateView() { mView.updateEverything(isTunerEnabled(), multiUserSwitchController.isMultiUserEnabled) } @@ -195,6 +243,10 @@ class FooterActionsController @Inject constructor( } else { userInfoController.removeCallback(onUserInfoChangedListener) } + if (featureFlags.isEnabled(Flags.NEW_FOOTER)) { + fgsManagerFooterController.setListening(listening) + securityFooterController.setListening(listening) + } } fun disable(state2: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java index 55d4a53ced7b2..0fe909552cb15 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java @@ -20,12 +20,17 @@ import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FGS_MANAGER_FOO 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.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.qs.dagger.QSScope; import java.util.concurrent.Executor; @@ -35,9 +40,11 @@ import javax.inject.Named; /** * Footer entry point for the foreground service manager */ +@QSScope public class QSFgsManagerFooter implements View.OnClickListener, FgsManagerController.OnDialogDismissedListener, - FgsManagerController.OnNumberOfPackagesChangedListener { + FgsManagerController.OnNumberOfPackagesChangedListener, + VisibilityChangedDispatcher { private final View mRootView; private final TextView mFooterText; @@ -50,20 +57,43 @@ public class QSFgsManagerFooter implements View.OnClickListener, private boolean mIsInitialized = false; private int mNumPackages; + private final View mTextContainer; + private final View mNumberContainer; + private final TextView mNumberView; + private final ImageView mDotView; + + @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); - ImageView icon = mRootView.findViewById(R.id.primary_footer_icon); - icon.setImageResource(R.drawable.ic_info_outline); + 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); 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; @@ -88,6 +118,12 @@ public class QSFgsManagerFooter implements View.OnClickListener, } } + @Override + public void setOnVisibilityChangedListener( + @Nullable OnVisibilityChangedListener onVisibilityChangedListener) { + mVisibilityChangedListener = onVisibilityChangedListener; + } + @Override public void onClick(View view) { mFgsManagerController.showDialog(mRootView); @@ -103,11 +139,19 @@ public class QSFgsManagerFooter implements View.OnClickListener, public void handleRefreshState() { mMainExecutor.execute(() -> { - mFooterText.setText(mContext.getResources().getQuantityString( - R.plurals.fgs_manager_footer_label, mNumPackages, mNumPackages)); + CharSequence text = mContext.getResources().getQuantityString( + R.plurals.fgs_manager_footer_label, mNumPackages, mNumPackages); + mFooterText.setText(text); + mNumberView.setText(Integer.toString(mNumPackages)); + mNumberView.setContentDescription(text); if (mFgsManagerController.shouldUpdateFooterVisibility()) { mRootView.setVisibility(mNumPackages > 0 && mFgsManagerController.isAvailable() ? View.VISIBLE : View.GONE); + mDotView.setVisibility( + mFgsManagerController.getChangesSinceDialog() ? View.VISIBLE : View.GONE); + if (mVisibilityChangedListener != null) { + mVisibilityChangedListener.onVisibilityChanged(mRootView.getVisibility()); + } } }); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 03149e34a6896..418c4ae023ca0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -69,6 +69,7 @@ public class QSPanelController extends QSPanelControllerBase { private final BrightnessController mBrightnessController; private final BrightnessSliderController mBrightnessSliderController; private final BrightnessMirrorHandler mBrightnessMirrorHandler; + private final FeatureFlags mFeatureFlags; private boolean mGridContentVisible = true; @@ -116,13 +117,13 @@ public class QSPanelController extends QSPanelControllerBase { mQsTileRevealControllerFactory = qsTileRevealControllerFactory; mFalsingManager = falsingManager; mCommandQueue = commandQueue; - mQsSecurityFooter.setHostEnvironment(qstileHost); mBrightnessSliderController = brightnessSliderFactory.create(getContext(), mView); mView.setBrightnessView(mBrightnessSliderController.getRootView()); mBrightnessController = brightnessControllerFactory.create(mBrightnessSliderController); mBrightnessMirrorHandler = new BrightnessMirrorHandler(mBrightnessController); + mFeatureFlags = featureFlags; view.setUseNewFooter(featureFlags.isEnabled(Flags.NEW_FOOTER)); } @@ -153,8 +154,10 @@ public class QSPanelController extends QSPanelControllerBase { refreshAllTiles(); } mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener); - mView.setFgsManagerFooter(mQSFgsManagerFooter.getView()); - mView.setSecurityFooter(mQsSecurityFooter.getView(), mShouldUseSplitNotificationShade); + if (!mFeatureFlags.isEnabled(Flags.NEW_FOOTER)) { + mView.setSecurityFooter(mQsSecurityFooter.getView(), mShouldUseSplitNotificationShade); + mView.setFgsManagerFooter(mQSFgsManagerFooter.getView()); + } switchTileLayout(true); mBrightnessMirrorHandler.onQsPanelAttached(); @@ -195,8 +198,10 @@ public class QSPanelController extends QSPanelControllerBase { refreshAllTiles(); } - mQSFgsManagerFooter.setListening(listening); - mQsSecurityFooter.setListening(listening); + if (!mFeatureFlags.isEnabled(Flags.NEW_FOOTER)) { + mQSFgsManagerFooter.setListening(listening); + mQsSecurityFooter.setListening(listening); + } // Set the listening as soon as the QS fragment starts listening regardless of the //expansion, so it will update the current brightness before the slider is visible. diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java index 9e17c121a79f2..fb55cd2d22453 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java @@ -80,6 +80,7 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.systemui.FontSizeUtils; import com.android.systemui.R; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -88,11 +89,14 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.SecurityController; +import java.util.concurrent.atomic.AtomicBoolean; + import javax.inject.Inject; import javax.inject.Named; @QSScope -class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListener { +class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListener, + VisibilityChangedDispatcher { protected static final String TAG = "QSSecurityFooter"; protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final boolean DEBUG_FORCE_VISIBLE = false; @@ -107,11 +111,16 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen private final ActivityStarter mActivityStarter; private final Handler mMainHandler; private final UserTracker mUserTracker; + private final DialogLaunchAnimator mDialogLaunchAnimator; + + private final AtomicBoolean mShouldUseSettingsButton = new AtomicBoolean(false); private AlertDialog mDialog; - private QSTileHost mHost; protected H mHandler; + // Does it move between footer and header? Remove this once all the flagging is removed + private boolean mIsMovable = true; + private boolean mIsVisible; @Nullable private CharSequence mFooterTextContent = null; @@ -119,10 +128,14 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen @Nullable private Drawable mPrimaryFooterIconDrawable; + @Nullable + private VisibilityChangedDispatcher.OnVisibilityChangedListener mVisibilityChangedListener; + @Inject QSSecurityFooter(@Named(QS_SECURITY_FOOTER_VIEW) View rootView, UserTracker userTracker, @Main Handler mainHandler, ActivityStarter activityStarter, - SecurityController securityController, @Background Looper bgLooper) { + SecurityController securityController, DialogLaunchAnimator dialogLaunchAnimator, + @Background Looper bgLooper) { mRootView = rootView; mRootView.setOnClickListener(this); mFooterText = mRootView.findViewById(R.id.footer_text); @@ -135,10 +148,7 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen mSecurityController = securityController; mHandler = new H(bgLooper); mUserTracker = userTracker; - } - - public void setHostEnvironment(QSTileHost host) { - mHost = host; + mDialogLaunchAnimator = dialogLaunchAnimator; } public void setListening(boolean listening) { @@ -150,23 +160,31 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen } } + @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(); + if (mIsMovable) { + Resources r = mContext.getResources(); - mFooterText.setMaxLines(r.getInteger(R.integer.qs_security_footer_maxLines)); - int padding = r.getDimensionPixelSize(R.dimen.qs_footer_padding); - mRootView.setPaddingRelative(padding, padding, padding, padding); + mFooterText.setMaxLines(r.getInteger(R.integer.qs_security_footer_maxLines)); + int padding = r.getDimensionPixelSize(R.dimen.qs_footer_padding); + mRootView.setPaddingRelative(padding, padding, padding, padding); - int bottomMargin = r.getDimensionPixelSize(R.dimen.qs_footers_margin_bottom); - ViewGroup.MarginLayoutParams lp = - (ViewGroup.MarginLayoutParams) mRootView.getLayoutParams(); - lp.bottomMargin = bottomMargin; - lp.width = r.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT - ? MATCH_PARENT : WRAP_CONTENT; - mRootView.setLayoutParams(lp); + int bottomMargin = r.getDimensionPixelSize(R.dimen.qs_footers_margin_bottom); + ViewGroup.MarginLayoutParams lp = + (ViewGroup.MarginLayoutParams) mRootView.getLayoutParams(); + lp.bottomMargin = bottomMargin; + lp.width = r.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT + ? MATCH_PARENT : WRAP_CONTENT; + mRootView.setLayoutParams(lp); + } mRootView.setBackground(mContext.getDrawable(R.drawable.qs_security_footer_background)); } @@ -455,23 +473,27 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_NEGATIVE) { final Intent intent = new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS); - mDialog.dismiss(); + dialog.dismiss(); // This dismisses the shade on opening the activity mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); } } private void createDialog() { - mDialog = new SystemUIDialog(mContext, 0); // Use mContext theme - mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); - mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this); - mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getNegativeButton(), this); + mShouldUseSettingsButton.set(false); + final View view = createDialogView(); + mMainHandler.post(() -> { + mDialog = new SystemUIDialog(mContext, 0); // Use mContext theme + mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this); + mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, + mShouldUseSettingsButton.get() ? getSettingsButton() : getNegativeButton(), + this); - mDialog.setView(createDialogView()); + mDialog.setView(view); - mDialog.show(); - mDialog.getWindow().setLayout(MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT); + mDialogLaunchAnimator.showFromView(mDialog, mRootView); + }); } @VisibleForTesting @@ -510,7 +532,7 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen TextView deviceManagementWarning = (TextView) dialogView.findViewById(R.id.device_management_warning); deviceManagementWarning.setText(managementMessage); - mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getSettingsButton(), this); + mShouldUseSettingsButton.set(true); } // ca certificate section @@ -782,6 +804,9 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen mFooterText.setText(mFooterTextContent); } mRootView.setVisibility(mIsVisible || DEBUG_FORCE_VISIBLE ? View.VISIBLE : View.GONE); + if (mVisibilityChangedListener != null) { + mVisibilityChangedListener.onVisibilityChanged(mRootView.getVisibility()); + } } }; @@ -814,7 +839,6 @@ class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListen } catch (Throwable t) { final String error = "Error in " + name; Log.w(TAG, error, t); - mHost.warn(error, t); } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/VisibilityChangedDispatcher.kt b/packages/SystemUI/src/com/android/systemui/qs/VisibilityChangedDispatcher.kt new file mode 100644 index 0000000000000..73362cec701e9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/VisibilityChangedDispatcher.kt @@ -0,0 +1,28 @@ +/* + * 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 + +/** + * Dispatches events that set the visibility from the controller. + */ +interface VisibilityChangedDispatcher { + + fun setOnVisibilityChangedListener(onVisibilityChangedListener: OnVisibilityChangedListener?) + + fun interface OnVisibilityChangedListener { + fun onVisibilityChanged(visibility: Int) + } +} \ No newline at end of file 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 776ee1021db28..816a3870eab0c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java @@ -193,6 +193,6 @@ public interface QSFragmentModule { @QSThemedContext LayoutInflater layoutInflater, QSPanel qsPanel ) { - return layoutInflater.inflate(R.layout.quick_settings_security_footer, qsPanel, false); + return layoutInflater.inflate(R.layout.fgs_footer, qsPanel, false); } } \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt b/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt index 0e04871106ef8..cfceefa2006c4 100644 --- a/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/util/DualHeightHorizontalLinearLayout.kt @@ -65,6 +65,17 @@ class DualHeightHorizontalLinearLayout @JvmOverloads constructor( private var initialPadding = mPaddingTop // All vertical padding is the same + private var originalMaxLines = 1 + var alwaysSingleLine: Boolean = false + set(value) { + field = value + if (field) { + textView?.setSingleLine() + } else { + textView?.maxLines = originalMaxLines + } + } + init { if (orientation != HORIZONTAL) { throw IllegalStateException("This view should always have horizontal orientation") @@ -120,7 +131,7 @@ class DualHeightHorizontalLinearLayout @JvmOverloads constructor( override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) textView?.let { tv -> - if (tv.lineCount < 2) { + if (tv.lineCount < 2 || alwaysSingleLine) { setMeasuredDimension(measuredWidth, singleLineHeightPx) mPaddingBottom = 0 mPaddingTop = 0 @@ -133,7 +144,9 @@ class DualHeightHorizontalLinearLayout @JvmOverloads constructor( override fun onFinishInflate() { super.onFinishInflate() - textView = findViewById(textViewId) + textView = findViewById(textViewId)?.also { + originalMaxLines = it.maxLines + } } override fun onConfigurationChanged(newConfig: Configuration?) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt index f5fa0d030901e..91a9f9e5ef36d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt @@ -64,6 +64,10 @@ class FooterActionsControllerTest : LeakCheckedTest() { private lateinit var uiEventLogger: UiEventLogger @Mock private lateinit var featureFlags: FeatureFlags + @Mock + private lateinit var securityFooterController: QSSecurityFooter + @Mock + private lateinit var fgsManagerController: QSFgsManagerFooter private lateinit var controller: FooterActionsController @@ -90,7 +94,8 @@ class FooterActionsControllerTest : LeakCheckedTest() { controller = FooterActionsController(view, multiUserSwitchControllerFactory, activityStarter, userManager, userTracker, userInfoController, - deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService, + deviceProvisionedController, securityFooterController, fgsManagerController, + falsingManager, metricsLogger, fakeTunerService, globalActionsDialog, uiEventLogger, showPMLiteButton = true, fakeSettings, Handler(testableLooper.looper), featureFlags) controller.init() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java index 770cf2ca03313..2b7fa42903c4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java @@ -22,12 +22,14 @@ import static junit.framework.Assert.assertNotNull; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; 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.app.AlertDialog; import android.content.ComponentName; import android.content.DialogInterface; import android.content.pm.UserInfo; @@ -50,6 +52,7 @@ import android.widget.TextView; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.policy.SecurityController; @@ -57,10 +60,13 @@ import com.android.systemui.statusbar.policy.SecurityController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; 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 @@ -94,20 +100,24 @@ public class QSSecurityFooterTest extends SysuiTestCase { private UserTracker mUserTracker; @Mock private ActivityStarter mActivityStarter; + @Mock + private DialogLaunchAnimator mDialogLaunchAnimator; + + private TestableLooper mTestableLooper; @Before public void setUp() { MockitoAnnotations.initMocks(this); - Looper looper = TestableLooper.get(this).getLooper(); + mTestableLooper = TestableLooper.get(this); + Looper looper = mTestableLooper.getLooper(); when(mUserTracker.getUserInfo()).thenReturn(mock(UserInfo.class)); mRootView = (ViewGroup) new LayoutInflaterBuilder(mContext) .replace("ImageView", TestableImageView.class) .build().inflate(R.layout.quick_settings_security_footer, null, false); mFooter = new QSSecurityFooter(mRootView, mUserTracker, new Handler(looper), - mActivityStarter, mSecurityController, looper); + mActivityStarter, mSecurityController, mDialogLaunchAnimator, looper); mFooterText = mRootView.findViewById(R.id.footer_text); mPrimaryFooterIcon = mRootView.findViewById(R.id.primary_footer_icon); - mFooter.setHostEnvironment(null); when(mSecurityController.getDeviceOwnerComponentOnAnyUser()) .thenReturn(DEVICE_OWNER_COMPONENT); @@ -650,8 +660,6 @@ public class QSSecurityFooterTest extends SysuiTestCase { @Test public void testNoClickWhenGone() { - QSTileHost mockHost = mock(QSTileHost.class); - mFooter.setHostEnvironment(mockHost); mFooter.refreshState(); TestableLooper.get(this).processAllMessages(); @@ -660,7 +668,7 @@ public class QSSecurityFooterTest extends SysuiTestCase { mFooter.onClick(mFooter.getView()); // Proxy for dialog being created - verify(mockHost, never()).collapsePanels(); + verify(mDialogLaunchAnimator, never()).showFromView(any(), any()); } @Test @@ -699,6 +707,16 @@ public class QSSecurityFooterTest extends SysuiTestCase { assertEquals(PARENTAL_CONTROLS_LABEL, textView.getText()); } + @Test + public void testDialogUsesDialogLauncher() { + when(mSecurityController.isDeviceManaged()).thenReturn(true); + mFooter.onClick(mRootView); + + mTestableLooper.processAllMessages(); + + verify(mDialogLaunchAnimator).showFromView(any(), eq(mRootView)); + } + @Test public void testCreateDialogViewForFinancedDevice() { when(mSecurityController.isDeviceManaged()).thenReturn(true); @@ -707,12 +725,6 @@ public class QSSecurityFooterTest extends SysuiTestCase { when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT)) .thenReturn(DEVICE_OWNER_TYPE_FINANCED); - // Initialize AlertDialog which sets the text for the negative button, which is used when - // creating the dialog for a financed device. - mFooter.showDeviceMonitoringDialog(); - // The above statement would display the Quick Settings dialog which requires user input, - // so simulate the press to continue with the unit test (otherwise, it is stuck). - mFooter.onClick(null, DialogInterface.BUTTON_NEGATIVE); View view = mFooter.createDialogView(); TextView managementSubtitle = view.findViewById(R.id.device_management_subtitle); @@ -727,6 +739,49 @@ public class QSSecurityFooterTest extends SysuiTestCase { mFooter.getSettingsButton()); } + @Test + public void testFinancedDeviceUsesSettingsButtonText() { + when(mSecurityController.isDeviceManaged()).thenReturn(true); + when(mSecurityController.getDeviceOwnerOrganizationName()) + .thenReturn(MANAGING_ORGANIZATION); + when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT)) + .thenReturn(DEVICE_OWNER_TYPE_FINANCED); + + mFooter.showDeviceMonitoringDialog(); + ArgumentCaptor dialogCaptor = ArgumentCaptor.forClass(AlertDialog.class); + + mTestableLooper.processAllMessages(); + verify(mDialogLaunchAnimator).showFromView(dialogCaptor.capture(), any()); + + AlertDialog dialog = dialogCaptor.getValue(); + dialog.create(); + + assertEquals(mFooter.getSettingsButton(), + dialog.getButton(DialogInterface.BUTTON_NEGATIVE).getText()); + + dialog.dismiss(); + } + + @Test + public void testVisibilityListener() { + final AtomicInteger lastVisibility = new AtomicInteger(-1); + VisibilityChangedDispatcher.OnVisibilityChangedListener listener = + (VisibilityChangedDispatcher.OnVisibilityChangedListener) 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()); + } + + private CharSequence addLink(CharSequence description) { final SpannableStringBuilder message = new SpannableStringBuilder(); message.append(description);