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
This commit is contained in:
95
packages/SystemUI/res-keyguard/layout/fgs_footer.xml
Normal file
95
packages/SystemUI/res-keyguard/layout/fgs_footer.xml
Normal file
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/qs_security_footer_single_line_height"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:clickable="true"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/fgs_text_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="@dimen/new_qs_footer_action_inset"
|
||||
android:background="@drawable/qs_security_footer_background"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="@dimen/qs_footer_padding"
|
||||
>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/primary_footer_icon"
|
||||
android:layout_width="@dimen/qs_footer_icon_size"
|
||||
android:layout_height="@dimen/qs_footer_icon_size"
|
||||
android:gravity="start"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_info_outline"
|
||||
android:tint="?android:attr/textColorSecondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/footer_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
|
||||
android:textColor="?android:attr/textColorSecondary"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/footer_icon"
|
||||
android:layout_width="@dimen/qs_footer_icon_size"
|
||||
android:layout_height="@dimen/qs_footer_icon_size"
|
||||
android:layout_marginStart="8dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@*android:drawable/ic_chevron_end"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?android:attr/textColorSecondary" />
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fgs_number_container"
|
||||
android:layout_width="@dimen/qs_footer_action_button_size"
|
||||
android:layout_height="@dimen/qs_footer_action_button_size"
|
||||
android:background="@drawable/qs_footer_action_circle"
|
||||
android:focusable="true"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fgs_number"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
|
||||
android:layout_gravity="center"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="18sp"/>
|
||||
<ImageView
|
||||
android:id="@+id/fgs_new"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:src="@drawable/new_fgs_dot"
|
||||
android:contentDescription="@string/fgs_dot_content_description"
|
||||
/>
|
||||
</FrameLayout>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -29,11 +29,13 @@
|
||||
android:layout_gravity="bottom"
|
||||
>
|
||||
|
||||
<View
|
||||
android:layout_height="1dp"
|
||||
<LinearLayout
|
||||
android:id="@+id/security_footers_container"
|
||||
android:orientation="horizontal"
|
||||
android:layout_height="@dimen/qs_footer_action_button_size"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
/>
|
||||
|
||||
<!-- Negative margin equal to -->
|
||||
<LinearLayout
|
||||
|
||||
22
packages/SystemUI/res/drawable/new_fgs_dot.xml
Normal file
22
packages/SystemUI/res/drawable/new_fgs_dot.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
** Copyright 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.
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval"
|
||||
android:width="12dp"
|
||||
android:height="12dp">
|
||||
<solid android:color="@*android:color/red" />
|
||||
</shape>
|
||||
@@ -15,8 +15,8 @@
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:insetTop="@dimen/qs_security_footer_background_inset"
|
||||
android:insetBottom="@dimen/qs_security_footer_background_inset"
|
||||
android:insetTop="@dimen/qs_footer_action_inset"
|
||||
android:insetBottom="@dimen/qs_footer_action_inset"
|
||||
>
|
||||
<ripple
|
||||
android:color="?android:attr/colorControlHighlight">
|
||||
|
||||
@@ -2375,6 +2375,9 @@
|
||||
<item quantity="one"><xliff:g id="count" example="1">%s</xliff:g> active app</item>
|
||||
<item quantity="other"><xliff:g id="count" example="2">%s</xliff:g> active apps</item>
|
||||
</plurals>
|
||||
<!-- Content description for a dot indicator in the running application indicating that there
|
||||
is new information [CHAR LIMIT=NONE] -->
|
||||
<string name="fgs_dot_content_description">New information</string>
|
||||
<!-- Title for dialog listing applications currently running [CHAR LIMIT=NONE]-->
|
||||
<string name="fgs_manager_dialog_title">Active apps</string>
|
||||
<!-- Label of the button to stop an app from running [CHAR LIMIT=12]-->
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
|
||||
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<QSPanel> {
|
||||
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<QSPanel> {
|
||||
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<QSPanel> {
|
||||
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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<TextView>(textViewId)?.also {
|
||||
originalMaxLines = it.maxLines
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration?) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<AlertDialog> 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);
|
||||
|
||||
Reference in New Issue
Block a user