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