Wire the new FooterActions implementation behind a flag
This CL wires the new footer actions implementation from ag/19678215 behind a new SysUI flag. This flag will be enabled by default in a follow-up CL. Bug: 242040009 Test: atest FooterActionsViewModelTest Test: atest FooterActionsScreenshotTest Change-Id: If23711e400a86ad247666a1532cb17616c01ed85
This commit is contained in:
@@ -6,4 +6,12 @@ packages/SystemUI/src/com/android/systemui/keyguard/data
|
||||
packages/SystemUI/src/com/android/systemui/keyguard/dagger
|
||||
packages/SystemUI/src/com/android/systemui/keyguard/domain
|
||||
packages/SystemUI/src/com/android/systemui/keyguard/shared
|
||||
packages/SystemUI/src/com/android/systemui/keyguard/ui
|
||||
packages/SystemUI/src/com/android/systemui/keyguard/ui
|
||||
packages/SystemUI/src/com/android/systemui/qs/footer
|
||||
packages/SystemUI/src/com/android/systemui/security
|
||||
packages/SystemUI/src/com/android/systemui/common/
|
||||
packages/SystemUI/tests/utils/src/com/android/systemui/qs/
|
||||
packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeSecurityController.kt
|
||||
packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeUserInfoController.kt
|
||||
packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/MockUserSwitcherControllerWrapper.kt
|
||||
packages/SystemUI/tests/src/com/android/systemui/qs/footer/
|
||||
@@ -14,6 +14,7 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!-- TODO(b/242040009): Remove this file. -->
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="0dp"
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
-->
|
||||
|
||||
<!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc -->
|
||||
<!-- TODO(b/242040009): Clean up this file. -->
|
||||
<com.android.systemui.qs.FooterActionsView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
@@ -25,7 +26,7 @@
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="@dimen/qs_footer_actions_bottom_padding"
|
||||
android:background="@drawable/qs_footer_actions_background"
|
||||
android:gravity="center_vertical"
|
||||
android:gravity="center_vertical|end"
|
||||
android:layout_gravity="bottom"
|
||||
>
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?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.
|
||||
-->
|
||||
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="@dimen/qs_footer_action_button_size"
|
||||
android:layout_height="@dimen/qs_footer_action_button_size"
|
||||
android:visibility="gone">
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="@dimen/qs_footer_icon_size"
|
||||
android:layout_height="@dimen/qs_footer_icon_size"
|
||||
android:layout_gravity="center"
|
||||
android:scaleType="centerInside" />
|
||||
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?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="@dimen/qs_footer_action_button_size"
|
||||
android:layout_height="@dimen/qs_footer_action_button_size"
|
||||
android:background="@drawable/qs_footer_action_circle"
|
||||
android:visibility="gone">
|
||||
<TextView
|
||||
android:id="@+id/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/new_dot"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:src="@drawable/fgs_dot"
|
||||
android:contentDescription="@string/fgs_dot_content_description" />
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,66 @@
|
||||
<?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.
|
||||
-->
|
||||
<LinearLayout
|
||||
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:orientation="horizontal"
|
||||
android:paddingHorizontal="@dimen/qs_footer_padding"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginEnd="@dimen/qs_footer_action_inset"
|
||||
android:background="@drawable/qs_security_footer_background"
|
||||
android:visibility="gone">
|
||||
<ImageView
|
||||
android:id="@+id/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/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/new_dot"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/fgs_dot"
|
||||
android:contentDescription="@string/fgs_dot_content_description"
|
||||
/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/chevron_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>
|
||||
@@ -14,6 +14,7 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!-- TODO(b/242040009): Remove this file. -->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="0dp"
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.common.ui
|
||||
|
||||
import android.view.View
|
||||
import com.android.systemui.common.shared.model.ContentDescription
|
||||
|
||||
object ContentDescriptionViewBinder {
|
||||
fun bind(
|
||||
contentDescription: ContentDescription,
|
||||
view: View,
|
||||
) {
|
||||
when (contentDescription) {
|
||||
is ContentDescription.Loaded -> view.contentDescription = contentDescription.description
|
||||
is ContentDescription.Resource -> {
|
||||
view.contentDescription = view.context.resources.getString(contentDescription.res)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,8 +48,12 @@ import com.android.systemui.navigationbar.NavigationBarComponent;
|
||||
import com.android.systemui.people.PeopleModule;
|
||||
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
|
||||
import com.android.systemui.privacy.PrivacyModule;
|
||||
import com.android.systemui.qs.FgsManagerController;
|
||||
import com.android.systemui.qs.FgsManagerControllerImpl;
|
||||
import com.android.systemui.qs.footer.dagger.FooterActionsModule;
|
||||
import com.android.systemui.recents.Recents;
|
||||
import com.android.systemui.screenshot.dagger.ScreenshotModule;
|
||||
import com.android.systemui.security.data.repository.SecurityRepositoryModule;
|
||||
import com.android.systemui.settings.dagger.MultiUserUtilsModule;
|
||||
import com.android.systemui.shade.ShadeController;
|
||||
import com.android.systemui.smartspace.dagger.SmartspaceModule;
|
||||
@@ -121,6 +125,7 @@ import dagger.Provides;
|
||||
DemoModeModule.class,
|
||||
FalsingModule.class,
|
||||
FlagsModule.class,
|
||||
FooterActionsModule.class,
|
||||
LogModule.class,
|
||||
MediaProjectionModule.class,
|
||||
PeopleHubModule.class,
|
||||
@@ -131,6 +136,7 @@ import dagger.Provides;
|
||||
ScreenshotModule.class,
|
||||
SensorModule.class,
|
||||
MultiUserUtilsModule.class,
|
||||
SecurityRepositoryModule.class,
|
||||
SettingsUtilModule.class,
|
||||
SmartRepliesInflationModule.class,
|
||||
SmartspaceModule.class,
|
||||
@@ -256,4 +262,7 @@ public abstract class SystemUIModule {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract FgsManagerController bindFgsManagerController(FgsManagerControllerImpl impl);
|
||||
}
|
||||
|
||||
@@ -147,6 +147,8 @@ public class Flags {
|
||||
public static final ResourceBooleanFlag FULL_SCREEN_USER_SWITCHER =
|
||||
new ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher);
|
||||
|
||||
public static final UnreleasedFlag NEW_FOOTER_ACTIONS = new UnreleasedFlag(507);
|
||||
|
||||
/***************************************/
|
||||
// 600- status bar
|
||||
public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER =
|
||||
|
||||
@@ -56,6 +56,7 @@ import javax.inject.Provider
|
||||
* determined by [buttonsVisibleState]
|
||||
*/
|
||||
@QSScope
|
||||
// TODO(b/242040009): Remove this file.
|
||||
internal class FooterActionsController @Inject constructor(
|
||||
view: FooterActionsView,
|
||||
multiUserSwitchControllerFactory: MultiUserSwitchController.Factory,
|
||||
|
||||
@@ -38,6 +38,7 @@ import com.android.systemui.statusbar.phone.MultiUserSwitch
|
||||
* in split shade mode visible also in collapsed state. May contain up to 5 buttons: settings,
|
||||
* edit tiles, power off and conditionally: user switch and tuner
|
||||
*/
|
||||
// TODO(b/242040009): Remove this file.
|
||||
class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) {
|
||||
private lateinit var settingsContainer: View
|
||||
private lateinit var multiUserSwitch: MultiUserSwitch
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import javax.inject.Inject
|
||||
|
||||
/** Controller for the footer actions. This manages the initialization of its dependencies. */
|
||||
@SysUISingleton
|
||||
class NewFooterActionsController
|
||||
@Inject
|
||||
// TODO(b/242040009): Rename this to FooterActionsController.
|
||||
constructor(
|
||||
private val fgsManagerController: FgsManagerController,
|
||||
) {
|
||||
fun init() {
|
||||
fgsManagerController.init()
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ import javax.inject.Named;
|
||||
/**
|
||||
* Footer entry point for the foreground service manager
|
||||
*/
|
||||
// TODO(b/242040009): Remove this file.
|
||||
@QSScope
|
||||
public class QSFgsManagerFooter implements View.OnClickListener,
|
||||
FgsManagerController.OnDialogDismissedListener,
|
||||
|
||||
@@ -36,6 +36,9 @@ import android.view.ViewTreeObserver;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LifecycleRegistry;
|
||||
|
||||
import com.android.keyguard.BouncerPanelExpansionCalculator;
|
||||
import com.android.systemui.Dumpable;
|
||||
@@ -43,6 +46,8 @@ import com.android.systemui.R;
|
||||
import com.android.systemui.animation.Interpolators;
|
||||
import com.android.systemui.animation.ShadeInterpolation;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.flags.FeatureFlags;
|
||||
import com.android.systemui.flags.Flags;
|
||||
import com.android.systemui.media.MediaHost;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
import com.android.systemui.plugins.qs.QS;
|
||||
@@ -50,6 +55,8 @@ import com.android.systemui.plugins.qs.QSContainerController;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.qs.customize.QSCustomizerController;
|
||||
import com.android.systemui.qs.dagger.QSFragmentComponent;
|
||||
import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
|
||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
|
||||
import com.android.systemui.statusbar.CommandQueue;
|
||||
import com.android.systemui.statusbar.StatusBarState;
|
||||
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
|
||||
@@ -104,6 +111,10 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
private final QSFragmentComponent.Factory mQsComponentFactory;
|
||||
private final QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger;
|
||||
private final QSTileHost mHost;
|
||||
private final FeatureFlags mFeatureFlags;
|
||||
private final NewFooterActionsController mNewFooterActionsController;
|
||||
private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
|
||||
private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner;
|
||||
private boolean mShowCollapsedOnKeyguard;
|
||||
private boolean mLastKeyguardAndExpanded;
|
||||
/**
|
||||
@@ -119,8 +130,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
private QSPanelController mQSPanelController;
|
||||
private QuickQSPanelController mQuickQSPanelController;
|
||||
private QSCustomizerController mQSCustomizerController;
|
||||
@Nullable
|
||||
private FooterActionsController mQSFooterActionController;
|
||||
@Nullable
|
||||
private FooterActionsViewModel mQSFooterActionsViewModel;
|
||||
@Nullable
|
||||
private ScrollListener mScrollListener;
|
||||
/**
|
||||
* When true, QS will translate from outside the screen. It will be clipped with parallax
|
||||
@@ -161,7 +175,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
KeyguardBypassController keyguardBypassController,
|
||||
QSFragmentComponent.Factory qsComponentFactory,
|
||||
QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger,
|
||||
FalsingManager falsingManager, DumpManager dumpManager) {
|
||||
FalsingManager falsingManager, DumpManager dumpManager, FeatureFlags featureFlags,
|
||||
NewFooterActionsController newFooterActionsController,
|
||||
FooterActionsViewModel.Factory footerActionsViewModelFactory) {
|
||||
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
|
||||
mQsMediaHost = qsMediaHost;
|
||||
mQqsMediaHost = qqsMediaHost;
|
||||
@@ -173,6 +189,10 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
mBypassController = keyguardBypassController;
|
||||
mStatusBarStateController = statusBarStateController;
|
||||
mDumpManager = dumpManager;
|
||||
mFeatureFlags = featureFlags;
|
||||
mNewFooterActionsController = newFooterActionsController;
|
||||
mFooterActionsViewModelFactory = footerActionsViewModelFactory;
|
||||
mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -193,11 +213,22 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(this);
|
||||
mQSPanelController = qsFragmentComponent.getQSPanelController();
|
||||
mQuickQSPanelController = qsFragmentComponent.getQuickQSPanelController();
|
||||
mQSFooterActionController = qsFragmentComponent.getQSFooterActionController();
|
||||
|
||||
mQSPanelController.init();
|
||||
mQuickQSPanelController.init();
|
||||
mQSFooterActionController.init();
|
||||
|
||||
if (mFeatureFlags.isEnabled(Flags.NEW_FOOTER_ACTIONS)) {
|
||||
mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */
|
||||
this);
|
||||
FooterActionsView footerActionsView = view.findViewById(R.id.qs_footer_actions);
|
||||
FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
|
||||
mListeningAndVisibilityLifecycleOwner);
|
||||
|
||||
mNewFooterActionsController.init();
|
||||
} else {
|
||||
mQSFooterActionController = qsFragmentComponent.getQSFooterActionController();
|
||||
mQSFooterActionController.init();
|
||||
}
|
||||
|
||||
mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
|
||||
mQSPanelScrollView.addOnLayoutChangeListener(
|
||||
@@ -283,6 +314,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
mDumpManager.unregisterDumpable(mContainer.getClass().getName());
|
||||
}
|
||||
mDumpManager.unregisterDumpable(getClass().getName());
|
||||
mListeningAndVisibilityLifecycleOwner.destroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -395,7 +427,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
mContainer.disable(state1, state2, animate);
|
||||
mHeader.disable(state1, state2, animate);
|
||||
mFooter.disable(state1, state2, animate);
|
||||
mQSFooterActionController.disable(state2);
|
||||
if (mQSFooterActionController != null) {
|
||||
mQSFooterActionController.disable(state2);
|
||||
}
|
||||
updateQsState();
|
||||
}
|
||||
|
||||
@@ -415,7 +449,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
boolean footerVisible = qsPanelVisible && (expanded || !keyguardShowing || mHeaderAnimating
|
||||
|| mShowCollapsedOnKeyguard);
|
||||
mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
|
||||
mQSFooterActionController.setVisible(footerVisible);
|
||||
if (mQSFooterActionController != null) {
|
||||
mQSFooterActionController.setVisible(footerVisible);
|
||||
} else {
|
||||
mQSFooterActionsViewModel.onVisibilityChangeRequested(footerVisible);
|
||||
}
|
||||
mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
|
||||
|| (expanded && !mStackScrollerOverscrolling));
|
||||
mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE);
|
||||
@@ -482,7 +520,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
}
|
||||
|
||||
mFooter.setKeyguardShowing(keyguardShowing);
|
||||
mQSFooterActionController.setKeyguardShowing(keyguardShowing);
|
||||
if (mQSFooterActionController != null) {
|
||||
mQSFooterActionController.setKeyguardShowing(keyguardShowing);
|
||||
}
|
||||
updateQsState();
|
||||
}
|
||||
|
||||
@@ -498,7 +538,10 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
if (DEBUG) Log.d(TAG, "setListening " + listening);
|
||||
mListening = listening;
|
||||
mQSContainerImplController.setListening(listening && mQsVisible);
|
||||
mQSFooterActionController.setListening(listening && mQsVisible);
|
||||
if (mQSFooterActionController != null) {
|
||||
mQSFooterActionController.setListening(listening && mQsVisible);
|
||||
}
|
||||
mListeningAndVisibilityLifecycleOwner.updateState();
|
||||
updateQsPanelControllerListening();
|
||||
}
|
||||
|
||||
@@ -511,6 +554,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
if (DEBUG) Log.d(TAG, "setQsVisible " + visible);
|
||||
mQsVisible = visible;
|
||||
setListening(mListening);
|
||||
mListeningAndVisibilityLifecycleOwner.updateState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -602,7 +646,12 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
|
||||
float footerActionsExpansion =
|
||||
onKeyguardAndExpanded ? 1 : mInSplitShade ? alphaProgress : expansion;
|
||||
mQSFooterActionController.setExpansion(footerActionsExpansion);
|
||||
if (mQSFooterActionController != null) {
|
||||
mQSFooterActionController.setExpansion(footerActionsExpansion);
|
||||
} else {
|
||||
mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion,
|
||||
mInSplitShade);
|
||||
}
|
||||
mQSPanelController.setRevealExpansion(expansion);
|
||||
mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
|
||||
mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
|
||||
@@ -714,7 +763,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
boolean customizing = isCustomizing();
|
||||
mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
|
||||
mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
|
||||
mQSFooterActionController.setVisible(!customizing);
|
||||
if (mQSFooterActionController != null) {
|
||||
mQSFooterActionController.setVisible(!customizing);
|
||||
} else {
|
||||
mQSFooterActionsViewModel.onVisibilityChangeRequested(!customizing);
|
||||
}
|
||||
mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
|
||||
// Let the panel know the position changed and it needs to update where notifications
|
||||
// and whatnot are.
|
||||
@@ -860,4 +913,56 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
|
||||
}
|
||||
return "GONE";
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link LifecycleOwner} whose state is driven by the current state of this fragment:
|
||||
*
|
||||
* - DESTROYED when the fragment is destroyed.
|
||||
* - CREATED when mListening == mQsVisible == false.
|
||||
* - STARTED when mListening == true && mQsVisible == false.
|
||||
* - RESUMED when mListening == true && mQsVisible == true.
|
||||
*/
|
||||
private class ListeningAndVisibilityLifecycleOwner implements LifecycleOwner {
|
||||
private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
|
||||
private boolean mDestroyed = false;
|
||||
|
||||
{
|
||||
updateState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lifecycle getLifecycle() {
|
||||
return mLifecycleRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state of the associated lifecycle. This should be called whenever
|
||||
* {@code mListening} or {@code mQsVisible} is changed.
|
||||
*/
|
||||
public void updateState() {
|
||||
if (mDestroyed) {
|
||||
mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mListening) {
|
||||
mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
|
||||
return;
|
||||
}
|
||||
|
||||
// mListening && !mQsVisible.
|
||||
if (!mQsVisible) {
|
||||
mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
|
||||
return;
|
||||
}
|
||||
|
||||
// mListening && mQsVisible.
|
||||
mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
mDestroyed = true;
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,11 +174,12 @@ public class QSSecurityFooterUtils implements DialogInterface.OnClickListener {
|
||||
|
||||
@Inject
|
||||
QSSecurityFooterUtils(
|
||||
@Application Context context, UserTracker userTracker, @Main Handler mainHandler,
|
||||
ActivityStarter activityStarter, SecurityController securityController,
|
||||
@Background Looper bgLooper, DialogLaunchAnimator dialogLaunchAnimator) {
|
||||
@Application Context context, DevicePolicyManager devicePolicyManager,
|
||||
UserTracker userTracker, @Main Handler mainHandler, ActivityStarter activityStarter,
|
||||
SecurityController securityController, @Background Looper bgLooper,
|
||||
DialogLaunchAnimator dialogLaunchAnimator) {
|
||||
mContext = context;
|
||||
mDpm = mContext.getSystemService(DevicePolicyManager.class);
|
||||
mDpm = devicePolicyManager;
|
||||
mUserTracker = userTracker;
|
||||
mMainHandler = mainHandler;
|
||||
mActivityStarter = activityStarter;
|
||||
|
||||
@@ -28,8 +28,6 @@ import com.android.systemui.battery.BatteryMeterView;
|
||||
import com.android.systemui.dagger.qualifiers.RootView;
|
||||
import com.android.systemui.plugins.qs.QS;
|
||||
import com.android.systemui.privacy.OngoingPrivacyChip;
|
||||
import com.android.systemui.qs.FgsManagerController;
|
||||
import com.android.systemui.qs.FgsManagerControllerImpl;
|
||||
import com.android.systemui.qs.FooterActionsView;
|
||||
import com.android.systemui.qs.QSContainerImpl;
|
||||
import com.android.systemui.qs.QSFooter;
|
||||
@@ -196,8 +194,4 @@ public interface QSFragmentModule {
|
||||
) {
|
||||
return layoutInflater.inflate(R.layout.fgs_footer, footerActionsView, false);
|
||||
}
|
||||
|
||||
/** */
|
||||
@Binds
|
||||
FgsManagerController bindFgsManagerController(FgsManagerControllerImpl impl);
|
||||
}
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package com.android.systemui.qs.footer.dagger
|
||||
|
||||
import com.android.systemui.qs.FgsManagerController
|
||||
import com.android.systemui.qs.FgsManagerControllerImpl
|
||||
import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository
|
||||
import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepositoryImpl
|
||||
import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository
|
||||
@@ -38,6 +36,4 @@ interface FooterActionsModule {
|
||||
): ForegroundServicesRepository
|
||||
|
||||
@Binds fun footerActionsInteractor(impl: FooterActionsInteractorImpl): FooterActionsInteractor
|
||||
|
||||
@Binds fun fgsManagerControllerImpl(impl: FgsManagerControllerImpl): FgsManagerController
|
||||
}
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* 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.footer.ui.binder
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.PorterDuff
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.common.ui.ContentDescriptionViewBinder
|
||||
import com.android.systemui.common.ui.IconViewBinder
|
||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||
import com.android.systemui.people.ui.view.PeopleViewBinder.bind
|
||||
import com.android.systemui.qs.FooterActionsView
|
||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
|
||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
|
||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
|
||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** A ViewBinder for [FooterActionsViewBinder]. */
|
||||
object FooterActionsViewBinder {
|
||||
/**
|
||||
* Create a [FooterActionsView] that can later be [bound][bind] to a [FooterActionsViewModel].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun create(context: Context): FooterActionsView {
|
||||
return LayoutInflater.from(context).inflate(R.layout.footer_actions, /* root= */ null)
|
||||
as FooterActionsView
|
||||
}
|
||||
|
||||
/** Bind [view] to [viewModel]. */
|
||||
@JvmStatic
|
||||
fun bind(
|
||||
view: FooterActionsView,
|
||||
viewModel: FooterActionsViewModel,
|
||||
qsVisibilityLifecycleOwner: LifecycleOwner,
|
||||
) {
|
||||
// Remove all children of the FooterActionsView that are used by the old implementation.
|
||||
// TODO(b/242040009): Clean up the XML once the old implementation is removed.
|
||||
view.removeAllViews()
|
||||
|
||||
// Add the views used by this new implementation.
|
||||
val context = view.context
|
||||
val inflater = LayoutInflater.from(context)
|
||||
|
||||
val securityHolder = TextButtonViewHolder.createAndAdd(inflater, view)
|
||||
val foregroundServicesWithTextHolder = TextButtonViewHolder.createAndAdd(inflater, view)
|
||||
val foregroundServicesWithNumberHolder = NumberButtonViewHolder.createAndAdd(inflater, view)
|
||||
val userSwitcherHolder = IconButtonViewHolder.createAndAdd(inflater, view, isLast = false)
|
||||
val settingsHolder =
|
||||
IconButtonViewHolder.createAndAdd(inflater, view, isLast = viewModel.power == null)
|
||||
|
||||
// Bind the static power and settings buttons.
|
||||
bindButton(settingsHolder, viewModel.settings)
|
||||
|
||||
if (viewModel.power != null) {
|
||||
val powerHolder = IconButtonViewHolder.createAndAdd(inflater, view, isLast = true)
|
||||
bindButton(powerHolder, viewModel.power)
|
||||
}
|
||||
|
||||
// There are 2 lifecycle scopes we are using here:
|
||||
// 1) The scope created by [repeatWhenAttached] when [view] is attached, and destroyed
|
||||
// when the [view] is detached. We use this as the parent scope for all our [viewModel]
|
||||
// state collection, given that we don't want to do any work when [view] is detached.
|
||||
// 2) The scope owned by [lifecycleOwner], which should be RESUMED only when Quick
|
||||
// Settings are visible. We use this to make sure we collect UI state only when the
|
||||
// View is visible.
|
||||
//
|
||||
// Given that we start our collection when the Quick Settings become visible, which happens
|
||||
// every time the user swipes down the shade, we remember our previous UI state already
|
||||
// bound to the UI to avoid binding the same values over and over for nothing.
|
||||
|
||||
// TODO(b/242040009): Look into using only a single scope.
|
||||
|
||||
var previousSecurity: FooterActionsSecurityButtonViewModel? = null
|
||||
var previousForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null
|
||||
var previousUserSwitcher: FooterActionsButtonViewModel? = null
|
||||
|
||||
view.repeatWhenAttached {
|
||||
val attachedScope = this.lifecycleScope
|
||||
|
||||
attachedScope.launch {
|
||||
// Listen for dialog requests as soon as we are attached, even when not visible.
|
||||
// TODO(b/242040009): Should this move somewhere else?
|
||||
launch { viewModel.observeDeviceMonitoringDialogRequests(view.context) }
|
||||
|
||||
// Make sure we set the correct visibility and alpha even when QS are not currently
|
||||
// shown.
|
||||
launch {
|
||||
viewModel.isVisible.collect { isVisible -> view.isInvisible = !isVisible }
|
||||
}
|
||||
|
||||
launch { viewModel.alpha.collect { view.alpha = it } }
|
||||
launch { viewModel.backgroundAlpha.collect { view.backgroundAlpha = it } }
|
||||
}
|
||||
|
||||
// Listen for model changes only when QS are visible.
|
||||
qsVisibilityLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
// Security.
|
||||
launch {
|
||||
viewModel.security.collect { security ->
|
||||
if (previousSecurity != security) {
|
||||
bindSecurity(securityHolder, security)
|
||||
previousSecurity = security
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Foreground services.
|
||||
launch {
|
||||
viewModel.foregroundServices.collect { foregroundServices ->
|
||||
if (previousForegroundServices != foregroundServices) {
|
||||
bindForegroundService(
|
||||
foregroundServicesWithNumberHolder,
|
||||
foregroundServicesWithTextHolder,
|
||||
foregroundServices,
|
||||
)
|
||||
previousForegroundServices = foregroundServices
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// User switcher.
|
||||
launch {
|
||||
viewModel.userSwitcher.collect { userSwitcher ->
|
||||
if (previousUserSwitcher != userSwitcher) {
|
||||
bindButton(userSwitcherHolder, userSwitcher)
|
||||
previousUserSwitcher = userSwitcher
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindSecurity(
|
||||
securityHolder: TextButtonViewHolder,
|
||||
security: FooterActionsSecurityButtonViewModel?,
|
||||
) {
|
||||
val securityView = securityHolder.view
|
||||
securityView.isVisible = security != null
|
||||
if (security == null) {
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure that the chevron is visible and that the button is clickable if there is a
|
||||
// listener.
|
||||
val chevron = securityHolder.chevron
|
||||
if (security.onClick != null) {
|
||||
securityView.isClickable = true
|
||||
securityView.setOnClickListener(security.onClick)
|
||||
chevron.isVisible = true
|
||||
} else {
|
||||
securityView.isClickable = false
|
||||
securityView.setOnClickListener(null)
|
||||
chevron.isVisible = false
|
||||
}
|
||||
|
||||
securityHolder.text.text = security.text
|
||||
securityHolder.newDot.isVisible = false
|
||||
IconViewBinder.bind(security.icon, securityHolder.icon)
|
||||
}
|
||||
|
||||
private fun bindForegroundService(
|
||||
foregroundServicesWithNumberHolder: NumberButtonViewHolder,
|
||||
foregroundServicesWithTextHolder: TextButtonViewHolder,
|
||||
foregroundServices: FooterActionsForegroundServicesButtonViewModel?,
|
||||
) {
|
||||
val foregroundServicesWithNumberView = foregroundServicesWithNumberHolder.view
|
||||
val foregroundServicesWithTextView = foregroundServicesWithTextHolder.view
|
||||
if (foregroundServices == null) {
|
||||
foregroundServicesWithNumberView.isVisible = false
|
||||
foregroundServicesWithTextView.isVisible = false
|
||||
return
|
||||
}
|
||||
|
||||
val foregroundServicesCount = foregroundServices.foregroundServicesCount
|
||||
if (foregroundServices.displayText) {
|
||||
// Button with text, icon and chevron.
|
||||
foregroundServicesWithNumberView.isVisible = false
|
||||
|
||||
foregroundServicesWithTextView.isVisible = true
|
||||
foregroundServicesWithTextView.setOnClickListener(foregroundServices.onClick)
|
||||
foregroundServicesWithTextHolder.text.text = foregroundServices.text
|
||||
foregroundServicesWithTextHolder.newDot.isVisible = foregroundServices.hasNewChanges
|
||||
} else {
|
||||
// Small button with the number only.
|
||||
foregroundServicesWithTextView.isVisible = false
|
||||
|
||||
foregroundServicesWithNumberView.visibility = View.VISIBLE
|
||||
foregroundServicesWithNumberView.setOnClickListener(foregroundServices.onClick)
|
||||
foregroundServicesWithNumberHolder.number.text = foregroundServicesCount.toString()
|
||||
foregroundServicesWithNumberHolder.number.contentDescription = foregroundServices.text
|
||||
foregroundServicesWithNumberHolder.newDot.isVisible = foregroundServices.hasNewChanges
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindButton(button: IconButtonViewHolder, model: FooterActionsButtonViewModel?) {
|
||||
val buttonView = button.view
|
||||
buttonView.isVisible = model != null
|
||||
if (model == null) {
|
||||
return
|
||||
}
|
||||
|
||||
buttonView.setBackgroundResource(model.background)
|
||||
buttonView.setOnClickListener(model.onClick)
|
||||
|
||||
val icon = model.icon
|
||||
val iconView = button.icon
|
||||
val contentDescription = model.contentDescription
|
||||
|
||||
IconViewBinder.bind(icon, iconView)
|
||||
ContentDescriptionViewBinder.bind(contentDescription, iconView)
|
||||
if (model.iconTint != null) {
|
||||
iconView.setColorFilter(model.iconTint, PorterDuff.Mode.SRC_IN)
|
||||
} else {
|
||||
iconView.clearColorFilter()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TextButtonViewHolder(val view: View) {
|
||||
val icon = view.requireViewById<ImageView>(R.id.icon)
|
||||
val text = view.requireViewById<TextView>(R.id.text)
|
||||
val newDot = view.requireViewById<ImageView>(R.id.new_dot)
|
||||
val chevron = view.requireViewById<ImageView>(R.id.chevron_icon)
|
||||
|
||||
companion object {
|
||||
fun createAndAdd(inflater: LayoutInflater, root: ViewGroup): TextButtonViewHolder {
|
||||
val view =
|
||||
inflater.inflate(
|
||||
R.layout.footer_actions_text_button,
|
||||
/* root= */ root,
|
||||
/* attachToRoot= */ false,
|
||||
)
|
||||
root.addView(view)
|
||||
return TextButtonViewHolder(view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class NumberButtonViewHolder(val view: View) {
|
||||
val number = view.requireViewById<TextView>(R.id.number)
|
||||
val newDot = view.requireViewById<ImageView>(R.id.new_dot)
|
||||
|
||||
companion object {
|
||||
fun createAndAdd(inflater: LayoutInflater, root: ViewGroup): NumberButtonViewHolder {
|
||||
val view =
|
||||
inflater.inflate(
|
||||
R.layout.footer_actions_number_button,
|
||||
/* root= */ root,
|
||||
/* attachToRoot= */ false,
|
||||
)
|
||||
root.addView(view)
|
||||
return NumberButtonViewHolder(view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class IconButtonViewHolder(val view: View) {
|
||||
val icon = view.requireViewById<ImageView>(R.id.icon)
|
||||
|
||||
companion object {
|
||||
fun createAndAdd(
|
||||
inflater: LayoutInflater,
|
||||
root: ViewGroup,
|
||||
isLast: Boolean,
|
||||
): IconButtonViewHolder {
|
||||
val view =
|
||||
inflater.inflate(
|
||||
R.layout.footer_actions_icon_button,
|
||||
/* root= */ root,
|
||||
/* attachToRoot= */ false,
|
||||
)
|
||||
|
||||
// All buttons have a background with an inset of qs_footer_action_inset, so the last
|
||||
// button must have a negative inset of -qs_footer_action_inset to compensate and be
|
||||
// aligned with its parent.
|
||||
val marginEnd =
|
||||
if (isLast) {
|
||||
-view.context.resources.getDimensionPixelSize(R.dimen.qs_footer_action_inset)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
val size =
|
||||
view.context.resources.getDimensionPixelSize(R.dimen.qs_footer_action_button_size)
|
||||
root.addView(
|
||||
view,
|
||||
LinearLayout.LayoutParams(size, size).apply { this.marginEnd = marginEnd },
|
||||
)
|
||||
return IconButtonViewHolder(view)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/** A ViewModel for the footer actions. */
|
||||
@@ -74,60 +75,65 @@ class FooterActionsViewModel(
|
||||
|
||||
/** The model for the security button. */
|
||||
val security: Flow<FooterActionsSecurityButtonViewModel?> =
|
||||
footerActionsInteractor.securityButtonConfig.map { config ->
|
||||
val (icon, text, isClickable) = config ?: return@map null
|
||||
FooterActionsSecurityButtonViewModel(
|
||||
icon,
|
||||
text,
|
||||
if (isClickable) this::onSecurityButtonClicked else null,
|
||||
)
|
||||
}
|
||||
footerActionsInteractor.securityButtonConfig
|
||||
.map { config ->
|
||||
val (icon, text, isClickable) = config ?: return@map null
|
||||
FooterActionsSecurityButtonViewModel(
|
||||
icon,
|
||||
text,
|
||||
if (isClickable) this::onSecurityButtonClicked else null,
|
||||
)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
/** The model for the foreground services button. */
|
||||
val foregroundServices: Flow<FooterActionsForegroundServicesButtonViewModel?> =
|
||||
combine(
|
||||
footerActionsInteractor.foregroundServicesCount,
|
||||
footerActionsInteractor.hasNewForegroundServices,
|
||||
security,
|
||||
) { foregroundServicesCount, hasNewChanges, securityModel ->
|
||||
if (foregroundServicesCount <= 0) {
|
||||
return@combine null
|
||||
}
|
||||
footerActionsInteractor.foregroundServicesCount,
|
||||
footerActionsInteractor.hasNewForegroundServices,
|
||||
security,
|
||||
) { foregroundServicesCount, hasNewChanges, securityModel ->
|
||||
if (foregroundServicesCount <= 0) {
|
||||
return@combine null
|
||||
}
|
||||
|
||||
val text =
|
||||
icuMessageFormat(
|
||||
context.resources,
|
||||
R.string.fgs_manager_footer_label,
|
||||
val text =
|
||||
icuMessageFormat(
|
||||
context.resources,
|
||||
R.string.fgs_manager_footer_label,
|
||||
foregroundServicesCount,
|
||||
)
|
||||
FooterActionsForegroundServicesButtonViewModel(
|
||||
foregroundServicesCount,
|
||||
text = text,
|
||||
displayText = securityModel == null,
|
||||
hasNewChanges = hasNewChanges,
|
||||
this::onForegroundServiceButtonClicked,
|
||||
)
|
||||
FooterActionsForegroundServicesButtonViewModel(
|
||||
foregroundServicesCount,
|
||||
text = text,
|
||||
displayText = securityModel == null,
|
||||
hasNewChanges = hasNewChanges,
|
||||
this::onForegroundServiceButtonClicked,
|
||||
)
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
/** The model for the user switcher button. */
|
||||
val userSwitcher: Flow<FooterActionsButtonViewModel?> =
|
||||
footerActionsInteractor.userSwitcherStatus.map { userSwitcherStatus ->
|
||||
when (userSwitcherStatus) {
|
||||
UserSwitcherStatusModel.Disabled -> null
|
||||
is UserSwitcherStatusModel.Enabled -> {
|
||||
if (userSwitcherStatus.currentUserImage == null) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"Skipped the addition of user switcher button because " +
|
||||
"currentUserImage is missing",
|
||||
)
|
||||
return@map null
|
||||
}
|
||||
footerActionsInteractor.userSwitcherStatus
|
||||
.map { userSwitcherStatus ->
|
||||
when (userSwitcherStatus) {
|
||||
UserSwitcherStatusModel.Disabled -> null
|
||||
is UserSwitcherStatusModel.Enabled -> {
|
||||
if (userSwitcherStatus.currentUserImage == null) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"Skipped the addition of user switcher button because " +
|
||||
"currentUserImage is missing",
|
||||
)
|
||||
return@map null
|
||||
}
|
||||
|
||||
userSwitcherButton(userSwitcherStatus)
|
||||
userSwitcherButton(userSwitcherStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
/** The model for the settings button. */
|
||||
val settings: FooterActionsButtonViewModel =
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.android.systemui.R;
|
||||
/**
|
||||
* Container for image of the multi user switcher (tappable).
|
||||
*/
|
||||
// TODO(b/242040009): Remove this file.
|
||||
public class MultiUserSwitch extends FrameLayout {
|
||||
public MultiUserSwitch(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
@@ -40,6 +40,7 @@ import com.android.systemui.util.ViewController;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** View Controller for {@link MultiUserSwitch}. */
|
||||
// TODO(b/242040009): Remove this file.
|
||||
public class MultiUserSwitchController extends ViewController<MultiUserSwitch> {
|
||||
private final UserManager mUserManager;
|
||||
private final UserSwitcherController mUserSwitcherController;
|
||||
|
||||
@@ -45,12 +45,15 @@ import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiBaseFragmentTest;
|
||||
import com.android.systemui.animation.ShadeInterpolation;
|
||||
import com.android.systemui.dump.DumpManager;
|
||||
import com.android.systemui.flags.FakeFeatureFlags;
|
||||
import com.android.systemui.flags.Flags;
|
||||
import com.android.systemui.media.MediaHost;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.qs.customize.QSCustomizerController;
|
||||
import com.android.systemui.qs.dagger.QSFragmentComponent;
|
||||
import com.android.systemui.qs.external.TileServiceRequestController;
|
||||
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
|
||||
import com.android.systemui.statusbar.CommandQueue;
|
||||
import com.android.systemui.statusbar.StatusBarState;
|
||||
import com.android.systemui.statusbar.phone.KeyguardBypassController;
|
||||
@@ -390,6 +393,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
|
||||
setUpMedia();
|
||||
setUpOther();
|
||||
|
||||
FakeFeatureFlags featureFlags = new FakeFeatureFlags();
|
||||
featureFlags.set(Flags.NEW_FOOTER_ACTIONS, false);
|
||||
return new QSFragment(
|
||||
new RemoteInputQuickSettingsDisabler(
|
||||
context, commandQueue, mock(ConfigurationController.class)),
|
||||
@@ -402,7 +407,10 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
|
||||
mQsComponentFactory,
|
||||
mock(QSFragmentDisableFlagsLogger.class),
|
||||
mFalsingManager,
|
||||
mock(DumpManager.class));
|
||||
mock(DumpManager.class),
|
||||
featureFlags,
|
||||
mock(NewFooterActionsController.class),
|
||||
mock(FooterActionsViewModel.Factory.class));
|
||||
}
|
||||
|
||||
private void setUpOther() {
|
||||
|
||||
@@ -124,8 +124,9 @@ public class QSSecurityFooterTest extends SysuiTestCase {
|
||||
mRootView = (ViewGroup) new LayoutInflaterBuilder(mContext)
|
||||
.replace("ImageView", TestableImageView.class)
|
||||
.build().inflate(R.layout.quick_settings_security_footer, null, false);
|
||||
mFooterUtils = new QSSecurityFooterUtils(getContext(), mUserTracker, mainHandler,
|
||||
mActivityStarter, mSecurityController, looper, mDialogLaunchAnimator);
|
||||
mFooterUtils = new QSSecurityFooterUtils(getContext(),
|
||||
getContext().getSystemService(DevicePolicyManager.class), mUserTracker,
|
||||
mainHandler, mActivityStarter, mSecurityController, looper, mDialogLaunchAnimator);
|
||||
mFooter = new QSSecurityFooter(mRootView, mainHandler, mSecurityController, looper,
|
||||
mBroadcastDispatcher, mFooterUtils);
|
||||
mFooterText = mRootView.findViewById(R.id.footer_text);
|
||||
|
||||
Reference in New Issue
Block a user