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