Merge "Adding privacy indicators to split shade header" into sc-v2-dev

This commit is contained in:
TreeHugger Robot
2021-12-01 11:33:21 +00:00
committed by Android (Google) Code Review
10 changed files with 369 additions and 239 deletions

View File

@@ -83,6 +83,17 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
systemui:textAppearance="@style/TextAppearance.QS.Status" /> systemui:textAppearance="@style/TextAppearance.QS.Status" />
<FrameLayout
android:id="@+id/privacy_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:minHeight="48dp"
android:layout_weight="1"
android:paddingStart="16dp">
<include layout="@layout/ongoing_privacy_chip" />
</FrameLayout>
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>

View File

@@ -0,0 +1,146 @@
package com.android.systemui.qs
import android.view.View
import com.android.internal.R
import com.android.internal.logging.UiEventLogger
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyChipEvent
import com.android.systemui.privacy.PrivacyDialogController
import com.android.systemui.privacy.PrivacyItem
import com.android.systemui.privacy.PrivacyItemController
import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.statusbar.phone.StatusIconContainer
import javax.inject.Inject
interface ChipVisibilityListener {
fun onChipVisibilityRefreshed(visible: Boolean)
}
/**
* Controls privacy icons/chip residing in QS header which show up when app is using camera,
* microphone or location.
* Manages their visibility depending on privacy signals coming from [PrivacyItemController].
*
* Unlike typical controller extending [com.android.systemui.util.ViewController] this view doesn't
* observe its attachment state because depending on where it is used, it might be never detached.
* Instead, parent controller should use [onParentVisible] and [onParentInvisible] to "activate" or
* "deactivate" this controller.
*/
class HeaderPrivacyIconsController @Inject constructor(
private val privacyItemController: PrivacyItemController,
private val uiEventLogger: UiEventLogger,
private val privacyChip: OngoingPrivacyChip,
private val privacyDialogController: PrivacyDialogController,
private val privacyLogger: PrivacyLogger,
private val iconContainer: StatusIconContainer
) {
var chipVisibilityListener: ChipVisibilityListener? = null
private var listening = false
private var micCameraIndicatorsEnabled = false
private var locationIndicatorsEnabled = false
private var privacyChipLogged = false
private val cameraSlot = privacyChip.resources.getString(R.string.status_bar_camera)
private val micSlot = privacyChip.resources.getString(R.string.status_bar_microphone)
private val locationSlot = privacyChip.resources.getString(R.string.status_bar_location)
private val picCallback: PrivacyItemController.Callback =
object : PrivacyItemController.Callback {
override fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>) {
privacyChip.privacyList = privacyItems
setChipVisibility(privacyItems.isNotEmpty())
}
override fun onFlagMicCameraChanged(flag: Boolean) {
if (micCameraIndicatorsEnabled != flag) {
micCameraIndicatorsEnabled = flag
update()
}
}
override fun onFlagLocationChanged(flag: Boolean) {
if (locationIndicatorsEnabled != flag) {
locationIndicatorsEnabled = flag
update()
}
}
private fun update() {
updatePrivacyIconSlots()
setChipVisibility(privacyChip.privacyList.isNotEmpty())
}
}
private fun getChipEnabled() = micCameraIndicatorsEnabled || locationIndicatorsEnabled
fun onParentVisible() {
privacyChip.setOnClickListener {
// If the privacy chip is visible, it means there were some indicators
uiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK)
privacyDialogController.showDialog(privacyChip.context)
}
setChipVisibility(privacyChip.visibility == View.VISIBLE)
micCameraIndicatorsEnabled = privacyItemController.micCameraAvailable
locationIndicatorsEnabled = privacyItemController.locationAvailable
// Ignore privacy icons because they show in the space above QQS
updatePrivacyIconSlots()
}
fun onParentInvisible() {
chipVisibilityListener = null
privacyChip.setOnClickListener(null)
}
fun startListening() {
listening = true
// Get the most up to date info
micCameraIndicatorsEnabled = privacyItemController.micCameraAvailable
locationIndicatorsEnabled = privacyItemController.locationAvailable
privacyItemController.addCallback(picCallback)
}
fun stopListening() {
listening = false
privacyItemController.removeCallback(picCallback)
privacyChipLogged = false
}
private fun setChipVisibility(visible: Boolean) {
if (visible && getChipEnabled()) {
privacyLogger.logChipVisible(true)
// Makes sure that the chip is logged as viewed at most once each time QS is opened
// mListening makes sure that the callback didn't return after the user closed QS
if (!privacyChipLogged && listening) {
privacyChipLogged = true
uiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW)
}
} else {
privacyLogger.logChipVisible(false)
}
privacyChip.visibility = if (visible) View.VISIBLE else View.GONE
chipVisibilityListener?.onChipVisibilityRefreshed(visible)
}
private fun updatePrivacyIconSlots() {
if (getChipEnabled()) {
if (micCameraIndicatorsEnabled) {
iconContainer.addIgnoredSlot(cameraSlot)
iconContainer.addIgnoredSlot(micSlot)
} else {
iconContainer.removeIgnoredSlot(cameraSlot)
iconContainer.removeIgnoredSlot(micSlot)
}
if (locationIndicatorsEnabled) {
iconContainer.addIgnoredSlot(locationSlot)
} else {
iconContainer.removeIgnoredSlot(locationSlot)
}
} else {
iconContainer.removeIgnoredSlot(cameraSlot)
iconContainer.removeIgnoredSlot(micSlot)
iconContainer.removeIgnoredSlot(locationSlot)
}
}
}

View File

@@ -373,7 +373,6 @@ public class QuickStatusBarHeader extends FrameLayout {
} }
void setChipVisibility(boolean visibility) { void setChipVisibility(boolean visibility) {
mPrivacyChip.setVisibility(visibility ? View.VISIBLE : View.GONE);
if (visibility) { if (visibility) {
// Animates the icons and battery indicator from alpha 0 to 1, when the chip is visible // Animates the icons and battery indicator from alpha 0 to 1, when the chip is visible
mIconsAlphaAnimator = mIconsAlphaAnimatorFixed; mIconsAlphaAnimator = mIconsAlphaAnimatorFixed;

View File

@@ -17,26 +17,14 @@
package com.android.systemui.qs; package com.android.systemui.qs;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import androidx.annotation.NonNull;
import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController; import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.privacy.OngoingPrivacyChip;
import com.android.systemui.privacy.PrivacyChipEvent;
import com.android.systemui.privacy.PrivacyDialogController;
import com.android.systemui.privacy.PrivacyItem;
import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.privacy.logging.PrivacyLogger;
import com.android.systemui.qs.carrier.QSCarrierGroupController; import com.android.systemui.qs.carrier.QSCarrierGroupController;
import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider; import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
@@ -54,23 +42,17 @@ import javax.inject.Inject;
* Controller for {@link QuickStatusBarHeader}. * Controller for {@link QuickStatusBarHeader}.
*/ */
@QSScope @QSScope
class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader> { class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader> implements
private static final String TAG = "QuickStatusBarHeader"; ChipVisibilityListener {
private final PrivacyItemController mPrivacyItemController;
private final ActivityStarter mActivityStarter;
private final UiEventLogger mUiEventLogger;
private final QSCarrierGroupController mQSCarrierGroupController; private final QSCarrierGroupController mQSCarrierGroupController;
private final QuickQSPanelController mQuickQSPanelController; private final QuickQSPanelController mQuickQSPanelController;
private final OngoingPrivacyChip mPrivacyChip;
private final Clock mClockView; private final Clock mClockView;
private final StatusBarIconController mStatusBarIconController; private final StatusBarIconController mStatusBarIconController;
private final DemoModeController mDemoModeController; private final DemoModeController mDemoModeController;
private final StatusIconContainer mIconContainer; private final StatusIconContainer mIconContainer;
private final StatusBarIconController.TintedIconManager mIconManager; private final StatusBarIconController.TintedIconManager mIconManager;
private final DemoMode mDemoModeReceiver; private final DemoMode mDemoModeReceiver;
private final PrivacyLogger mPrivacyLogger;
private final PrivacyDialogController mPrivacyDialogController;
private final QSExpansionPathInterpolator mQSExpansionPathInterpolator; private final QSExpansionPathInterpolator mQSExpansionPathInterpolator;
private final BatteryMeterViewController mBatteryMeterViewController; private final BatteryMeterViewController mBatteryMeterViewController;
private final FeatureFlags mFeatureFlags; private final FeatureFlags mFeatureFlags;
@@ -78,83 +60,31 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
private final VariableDateViewController mVariableDateViewControllerDateView; private final VariableDateViewController mVariableDateViewControllerDateView;
private final VariableDateViewController mVariableDateViewControllerClockDateView; private final VariableDateViewController mVariableDateViewControllerClockDateView;
private final HeaderPrivacyIconsController mPrivacyIconsController;
private boolean mListening; private boolean mListening;
private boolean mMicCameraIndicatorsEnabled;
private boolean mLocationIndicatorsEnabled;
private boolean mPrivacyChipLogged;
private final String mCameraSlot;
private final String mMicSlot;
private final String mLocationSlot;
private SysuiColorExtractor mColorExtractor; private SysuiColorExtractor mColorExtractor;
private ColorExtractor.OnColorsChangedListener mOnColorsChangedListener; private ColorExtractor.OnColorsChangedListener mOnColorsChangedListener;
private PrivacyItemController.Callback mPICCallback = new PrivacyItemController.Callback() {
@Override
public void onPrivacyItemsChanged(@NonNull List<PrivacyItem> privacyItems) {
mPrivacyChip.setPrivacyList(privacyItems);
setChipVisibility(!privacyItems.isEmpty());
}
@Override
public void onFlagMicCameraChanged(boolean flag) {
if (mMicCameraIndicatorsEnabled != flag) {
mMicCameraIndicatorsEnabled = flag;
update();
}
}
@Override
public void onFlagLocationChanged(boolean flag) {
if (mLocationIndicatorsEnabled != flag) {
mLocationIndicatorsEnabled = flag;
update();
}
}
private void update() {
updatePrivacyIconSlots();
setChipVisibility(!mPrivacyChip.getPrivacyList().isEmpty());
}
};
private View.OnClickListener mOnClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
if (v == mPrivacyChip) {
// If the privacy chip is visible, it means there were some indicators
mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK);
mPrivacyDialogController.showDialog(getContext());
}
}
};
@Inject @Inject
QuickStatusBarHeaderController(QuickStatusBarHeader view, QuickStatusBarHeaderController(QuickStatusBarHeader view,
PrivacyItemController privacyItemController, HeaderPrivacyIconsController headerPrivacyIconsController,
ActivityStarter activityStarter, UiEventLogger uiEventLogger,
StatusBarIconController statusBarIconController, StatusBarIconController statusBarIconController,
DemoModeController demoModeController, DemoModeController demoModeController,
QuickQSPanelController quickQSPanelController, QuickQSPanelController quickQSPanelController,
QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder, QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder,
PrivacyLogger privacyLogger,
SysuiColorExtractor colorExtractor, SysuiColorExtractor colorExtractor,
PrivacyDialogController privacyDialogController,
QSExpansionPathInterpolator qsExpansionPathInterpolator, QSExpansionPathInterpolator qsExpansionPathInterpolator,
BatteryMeterViewController batteryMeterViewController, BatteryMeterViewController batteryMeterViewController,
FeatureFlags featureFlags, FeatureFlags featureFlags,
VariableDateViewController.Factory variableDateViewControllerFactory, VariableDateViewController.Factory variableDateViewControllerFactory,
StatusBarContentInsetsProvider statusBarContentInsetsProvider) { StatusBarContentInsetsProvider statusBarContentInsetsProvider) {
super(view); super(view);
mPrivacyItemController = privacyItemController; mPrivacyIconsController = headerPrivacyIconsController;
mActivityStarter = activityStarter;
mUiEventLogger = uiEventLogger;
mStatusBarIconController = statusBarIconController; mStatusBarIconController = statusBarIconController;
mDemoModeController = demoModeController; mDemoModeController = demoModeController;
mQuickQSPanelController = quickQSPanelController; mQuickQSPanelController = quickQSPanelController;
mPrivacyLogger = privacyLogger;
mPrivacyDialogController = privacyDialogController;
mQSExpansionPathInterpolator = qsExpansionPathInterpolator; mQSExpansionPathInterpolator = qsExpansionPathInterpolator;
mBatteryMeterViewController = batteryMeterViewController; mBatteryMeterViewController = batteryMeterViewController;
mFeatureFlags = featureFlags; mFeatureFlags = featureFlags;
@@ -163,8 +93,6 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mQSCarrierGroupController = qsCarrierGroupControllerBuilder mQSCarrierGroupController = qsCarrierGroupControllerBuilder
.setQSCarrierGroup(mView.findViewById(R.id.carrier_group)) .setQSCarrierGroup(mView.findViewById(R.id.carrier_group))
.build(); .build();
mPrivacyChip = mView.findViewById(R.id.privacy_chip);
mClockView = mView.findViewById(R.id.clock); mClockView = mView.findViewById(R.id.clock);
mIconContainer = mView.findViewById(R.id.statusIcons); mIconContainer = mView.findViewById(R.id.statusIcons);
mVariableDateViewControllerDateView = variableDateViewControllerFactory.create( mVariableDateViewControllerDateView = variableDateViewControllerFactory.create(
@@ -183,10 +111,6 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
}; };
mColorExtractor.addOnColorsChangedListener(mOnColorsChangedListener); mColorExtractor.addOnColorsChangedListener(mOnColorsChangedListener);
mCameraSlot = getResources().getString(com.android.internal.R.string.status_bar_camera);
mMicSlot = getResources().getString(com.android.internal.R.string.status_bar_microphone);
mLocationSlot = getResources().getString(com.android.internal.R.string.status_bar_location);
// Don't need to worry about tuner settings for this icon // Don't need to worry about tuner settings for this icon
mBatteryMeterViewController.ignoreTunerUpdates(); mBatteryMeterViewController.ignoreTunerUpdates();
} }
@@ -198,20 +122,13 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
@Override @Override
protected void onViewAttached() { protected void onViewAttached() {
mPrivacyChip.setOnClickListener(mOnClickListener); mPrivacyIconsController.onParentVisible();
mPrivacyIconsController.setChipVisibilityListener(this);
mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
mLocationIndicatorsEnabled = mPrivacyItemController.getLocationAvailable();
// Ignore privacy icons because they show in the space above QQS
updatePrivacyIconSlots();
mIconContainer.addIgnoredSlot( mIconContainer.addIgnoredSlot(
getResources().getString(com.android.internal.R.string.status_bar_managed_profile)); getResources().getString(com.android.internal.R.string.status_bar_managed_profile));
mIconContainer.setShouldRestrictIcons(false); mIconContainer.setShouldRestrictIcons(false);
mStatusBarIconController.addIconGroup(mIconManager); mStatusBarIconController.addIconGroup(mIconManager);
setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE);
mView.setIsSingleCarrier(mQSCarrierGroupController.isSingleCarrier()); mView.setIsSingleCarrier(mQSCarrierGroupController.isSingleCarrier());
mQSCarrierGroupController mQSCarrierGroupController
.setOnSingleCarrierChangedListener(mView::setIsSingleCarrier); .setOnSingleCarrierChangedListener(mView::setIsSingleCarrier);
@@ -241,7 +158,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
@Override @Override
protected void onViewDetached() { protected void onViewDetached() {
mColorExtractor.removeOnColorsChangedListener(mOnColorsChangedListener); mColorExtractor.removeOnColorsChangedListener(mOnColorsChangedListener);
mPrivacyChip.setOnClickListener(null); mPrivacyIconsController.onParentInvisible();
mStatusBarIconController.removeIconGroup(mIconManager); mStatusBarIconController.removeIconGroup(mIconManager);
mQSCarrierGroupController.setOnSingleCarrierChangedListener(null); mQSCarrierGroupController.setOnSingleCarrierChangedListener(null);
mDemoModeController.removeCallback(mDemoModeReceiver); mDemoModeController.removeCallback(mDemoModeReceiver);
@@ -266,54 +183,15 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
} }
if (listening) { if (listening) {
// Get the most up to date info mPrivacyIconsController.startListening();
mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
mLocationIndicatorsEnabled = mPrivacyItemController.getLocationAvailable();
mPrivacyItemController.addCallback(mPICCallback);
} else { } else {
mPrivacyItemController.removeCallback(mPICCallback); mPrivacyIconsController.stopListening();
mPrivacyChipLogged = false;
} }
} }
private void setChipVisibility(boolean chipVisible) { @Override
if (chipVisible && getChipEnabled()) { public void onChipVisibilityRefreshed(boolean visible) {
mPrivacyLogger.logChipVisible(true); mView.setChipVisibility(visible);
// Makes sure that the chip is logged as viewed at most once each time QS is opened
// mListening makes sure that the callback didn't return after the user closed QS
if (!mPrivacyChipLogged && mListening) {
mPrivacyChipLogged = true;
mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW);
}
} else {
mPrivacyLogger.logChipVisible(false);
}
mView.setChipVisibility(chipVisible);
}
private void updatePrivacyIconSlots() {
if (getChipEnabled()) {
if (mMicCameraIndicatorsEnabled) {
mIconContainer.addIgnoredSlot(mCameraSlot);
mIconContainer.addIgnoredSlot(mMicSlot);
} else {
mIconContainer.removeIgnoredSlot(mCameraSlot);
mIconContainer.removeIgnoredSlot(mMicSlot);
}
if (mLocationIndicatorsEnabled) {
mIconContainer.addIgnoredSlot(mLocationSlot);
} else {
mIconContainer.removeIgnoredSlot(mLocationSlot);
}
} else {
mIconContainer.removeIgnoredSlot(mCameraSlot);
mIconContainer.removeIgnoredSlot(mMicSlot);
mIconContainer.removeIgnoredSlot(mLocationSlot);
}
}
private boolean getChipEnabled() {
return mMicCameraIndicatorsEnabled || mLocationIndicatorsEnabled;
} }
public void setContentMargins(int marginStart, int marginEnd) { public void setContentMargins(int marginStart, int marginEnd) {

View File

@@ -26,6 +26,7 @@ import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.dagger.qualifiers.RootView; import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.qs.QS;
import com.android.systemui.privacy.OngoingPrivacyChip;
import com.android.systemui.qs.FooterActionsController; import com.android.systemui.qs.FooterActionsController;
import com.android.systemui.qs.FooterActionsController.ExpansionState; import com.android.systemui.qs.FooterActionsController.ExpansionState;
import com.android.systemui.qs.FooterActionsControllerBuilder; import com.android.systemui.qs.FooterActionsControllerBuilder;
@@ -39,6 +40,7 @@ import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QuickQSPanel; import com.android.systemui.qs.QuickQSPanel;
import com.android.systemui.qs.QuickStatusBarHeader; import com.android.systemui.qs.QuickStatusBarHeader;
import com.android.systemui.qs.customize.QSCustomizer; import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import javax.inject.Named; import javax.inject.Named;
@@ -189,4 +191,18 @@ public interface QSFragmentModule {
static boolean providesQSUsingMediaPlayer(Context context) { static boolean providesQSUsingMediaPlayer(Context context) {
return useQsMediaPlayer(context); return useQsMediaPlayer(context);
} }
/** */
@Provides
@QSScope
static OngoingPrivacyChip providesPrivacyChip(QuickStatusBarHeader qsHeader) {
return qsHeader.findViewById(R.id.privacy_chip);
}
/** */
@Provides
@QSScope
static StatusIconContainer providesStatusIconContainer(QuickStatusBarHeader qsHeader) {
return qsHeader.findViewById(R.id.statusIcons);
}
} }

View File

@@ -23,6 +23,7 @@ import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.FeatureFlags
import com.android.systemui.qs.HeaderPrivacyIconsController
import com.android.systemui.qs.carrier.QSCarrierGroupController import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope
import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.SPLIT_SHADE_BATTERY_CONTROLLER import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.SPLIT_SHADE_BATTERY_CONTROLLER
@@ -34,6 +35,7 @@ import javax.inject.Named
class SplitShadeHeaderController @Inject constructor( class SplitShadeHeaderController @Inject constructor(
@Named(SPLIT_SHADE_HEADER) private val statusBar: View, @Named(SPLIT_SHADE_HEADER) private val statusBar: View,
private val statusBarIconController: StatusBarIconController, private val statusBarIconController: StatusBarIconController,
private val privacyIconsController: HeaderPrivacyIconsController,
qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder, qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
featureFlags: FeatureFlags, featureFlags: FeatureFlags,
@Named(SPLIT_SHADE_BATTERY_CONTROLLER) batteryMeterViewController: BatteryMeterViewController @Named(SPLIT_SHADE_BATTERY_CONTROLLER) batteryMeterViewController: BatteryMeterViewController
@@ -63,8 +65,7 @@ class SplitShadeHeaderController @Inject constructor(
return return
} }
field = value field = value
updateVisibility() onShadeExpandedChanged()
updatePosition()
} }
var splitShadeMode = false var splitShadeMode = false
@@ -73,8 +74,7 @@ class SplitShadeHeaderController @Inject constructor(
return return
} }
field = value field = value
updateVisibility() onSplitShadeModeChanged()
updateConstraints()
} }
var shadeExpandedFraction = -1f var shadeExpandedFraction = -1f
@@ -124,6 +124,26 @@ class SplitShadeHeaderController @Inject constructor(
updateConstraints() updateConstraints()
} }
private fun onShadeExpandedChanged() {
if (shadeExpanded) {
privacyIconsController.startListening()
} else {
privacyIconsController.stopListening()
}
updateVisibility()
updatePosition()
}
private fun onSplitShadeModeChanged() {
if (splitShadeMode) {
privacyIconsController.onParentVisible()
} else {
privacyIconsController.onParentInvisible()
}
updateVisibility()
updateConstraints()
}
private fun updateVisibility() { private fun updateVisibility() {
val visibility = if (!splitShadeMode && !combinedHeaders) { val visibility = if (!splitShadeMode && !combinedHeaders) {
View.GONE View.GONE
@@ -166,4 +186,4 @@ class SplitShadeHeaderController @Inject constructor(
statusBarIconController.removeIconGroup(iconManager) statusBarIconController.removeIconGroup(iconManager)
} }
} }
} }

View File

@@ -32,6 +32,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.privacy.OngoingPrivacyChip;
import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.NotificationShelfController;
@@ -49,6 +50,7 @@ import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.phone.TapAgainView; import com.android.systemui.statusbar.phone.TapAgainView;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger;
@@ -170,6 +172,21 @@ public abstract class StatusBarViewModule {
return v; return v;
} }
/** */
@Provides
@StatusBarComponent.StatusBarScope
public static OngoingPrivacyChip getSplitShadeOngoingPrivacyChip(
@Named(SPLIT_SHADE_HEADER) View header) {
return header.findViewById(R.id.privacy_chip);
}
/** */
@Provides
@StatusBarComponent.StatusBarScope
static StatusIconContainer providesStatusIconContainer(@Named(SPLIT_SHADE_HEADER) View header) {
return header.findViewById(R.id.statusIcons);
}
/** */ /** */
@Provides @Provides
@StatusBarComponent.StatusBarScope @StatusBarComponent.StatusBarScope

View File

@@ -0,0 +1,128 @@
package com.android.systemui.qs
import android.content.Context
import android.testing.AndroidTestingRunner
import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyDialogController
import com.android.systemui.privacy.PrivacyItemController
import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
class HeaderPrivacyIconsControllerTest : SysuiTestCase() {
@Mock
private lateinit var privacyItemController: PrivacyItemController
@Mock
private lateinit var uiEventLogger: UiEventLogger
@Mock
private lateinit var privacyChip: OngoingPrivacyChip
@Mock
private lateinit var privacyDialogController: PrivacyDialogController
@Mock
private lateinit var privacyLogger: PrivacyLogger
@Mock
private lateinit var iconContainer: StatusIconContainer
private lateinit var cameraSlotName: String
private lateinit var microphoneSlotName: String
private lateinit var locationSlotName: String
private lateinit var controller: HeaderPrivacyIconsController
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(privacyChip.context).thenReturn(context)
whenever(privacyChip.resources).thenReturn(context.resources)
cameraSlotName = context.getString(com.android.internal.R.string.status_bar_camera)
microphoneSlotName = context.getString(com.android.internal.R.string.status_bar_microphone)
locationSlotName = context.getString(com.android.internal.R.string.status_bar_location)
controller = HeaderPrivacyIconsController(
privacyItemController,
uiEventLogger,
privacyChip,
privacyDialogController,
privacyLogger,
iconContainer
)
}
@Test
fun testIgnoredSlotsOnParentVisible_noIndicators() {
setPrivacyController(micCamera = false, location = false)
controller.onParentVisible()
verify(iconContainer).removeIgnoredSlot(cameraSlotName)
verify(iconContainer).removeIgnoredSlot(microphoneSlotName)
verify(iconContainer).removeIgnoredSlot(locationSlotName)
}
@Test
fun testIgnoredSlotsOnParentVisible_onlyMicCamera() {
setPrivacyController(micCamera = true, location = false)
controller.onParentVisible()
verify(iconContainer).addIgnoredSlot(cameraSlotName)
verify(iconContainer).addIgnoredSlot(microphoneSlotName)
verify(iconContainer).removeIgnoredSlot(locationSlotName)
}
@Test
fun testIgnoredSlotsOnParentVisible_onlyLocation() {
setPrivacyController(micCamera = false, location = true)
controller.onParentVisible()
verify(iconContainer).removeIgnoredSlot(cameraSlotName)
verify(iconContainer).removeIgnoredSlot(microphoneSlotName)
verify(iconContainer).addIgnoredSlot(locationSlotName)
}
@Test
fun testIgnoredSlotsOnParentVisible_locationMicCamera() {
setPrivacyController(micCamera = true, location = true)
controller.onParentVisible()
verify(iconContainer).addIgnoredSlot(cameraSlotName)
verify(iconContainer).addIgnoredSlot(microphoneSlotName)
verify(iconContainer).addIgnoredSlot(locationSlotName)
}
@Test
fun testPrivacyChipClicked() {
controller.onParentVisible()
val captor = argumentCaptor<View.OnClickListener>()
verify(privacyChip).setOnClickListener(capture(captor))
captor.value.onClick(privacyChip)
verify(privacyDialogController).showDialog(any(Context::class.java))
}
private fun setPrivacyController(micCamera: Boolean, location: Boolean) {
whenever(privacyItemController.micCameraAvailable).thenReturn(micCamera)
whenever(privacyItemController.locationAvailable).thenReturn(location)
}
}

View File

@@ -20,18 +20,12 @@ import android.content.Context
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import android.view.View import android.view.View
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.colorextraction.SysuiColorExtractor import com.android.systemui.colorextraction.SysuiColorExtractor
import com.android.systemui.demomode.DemoModeController import com.android.systemui.demomode.DemoModeController
import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.FeatureFlags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.OngoingPrivacyChip
import com.android.systemui.privacy.PrivacyDialogController
import com.android.systemui.privacy.PrivacyItemController
import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.qs.carrier.QSCarrierGroup import com.android.systemui.qs.carrier.QSCarrierGroup
import com.android.systemui.qs.carrier.QSCarrierGroupController import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
@@ -51,10 +45,10 @@ import org.junit.runner.RunWith
import org.mockito.Answers import org.mockito.Answers
import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.reset import org.mockito.Mockito.reset
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
@SmallTest @SmallTest
@@ -64,11 +58,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
@Mock @Mock
private lateinit var view: QuickStatusBarHeader private lateinit var view: QuickStatusBarHeader
@Mock @Mock
private lateinit var privacyItemController: PrivacyItemController private lateinit var privacyIconsController: HeaderPrivacyIconsController
@Mock
private lateinit var activityStarter: ActivityStarter
@Mock
private lateinit var uiEventLogger: UiEventLogger
@Mock @Mock
private lateinit var statusBarIconController: StatusBarIconController private lateinit var statusBarIconController: StatusBarIconController
@Mock @Mock
@@ -80,18 +70,12 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
@Mock @Mock
private lateinit var qsCarrierGroupController: QSCarrierGroupController private lateinit var qsCarrierGroupController: QSCarrierGroupController
@Mock @Mock
private lateinit var privacyLogger: PrivacyLogger
@Mock
private lateinit var colorExtractor: SysuiColorExtractor private lateinit var colorExtractor: SysuiColorExtractor
@Mock @Mock
private lateinit var iconContainer: StatusIconContainer private lateinit var iconContainer: StatusIconContainer
@Mock @Mock
private lateinit var qsCarrierGroup: QSCarrierGroup private lateinit var qsCarrierGroup: QSCarrierGroup
@Mock @Mock
private lateinit var privacyChip: OngoingPrivacyChip
@Mock
private lateinit var privacyDialogController: PrivacyDialogController
@Mock
private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory
@Mock @Mock
private lateinit var variableDateViewController: VariableDateViewController private lateinit var variableDateViewController: VariableDateViewController
@@ -114,10 +98,6 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
private lateinit var controller: QuickStatusBarHeaderController private lateinit var controller: QuickStatusBarHeaderController
private lateinit var cameraSlotName: String
private lateinit var microphoneSlotName: String
private lateinit var locationSlotName: String
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
@@ -130,25 +110,14 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
`when`(view.isAttachedToWindow).thenReturn(true) `when`(view.isAttachedToWindow).thenReturn(true)
`when`(view.context).thenReturn(context) `when`(view.context).thenReturn(context)
cameraSlotName = mContext.resources.getString(
com.android.internal.R.string.status_bar_camera)
microphoneSlotName = mContext.resources.getString(
com.android.internal.R.string.status_bar_microphone)
locationSlotName = mContext.resources.getString(
com.android.internal.R.string.status_bar_location)
controller = QuickStatusBarHeaderController( controller = QuickStatusBarHeaderController(
view, view,
privacyItemController, privacyIconsController,
activityStarter,
uiEventLogger,
statusBarIconController, statusBarIconController,
demoModeController, demoModeController,
quickQSPanelController, quickQSPanelController,
qsCarrierGroupControllerBuilder, qsCarrierGroupControllerBuilder,
privacyLogger,
colorExtractor, colorExtractor,
privacyDialogController,
qsExpansionPathInterpolator, qsExpansionPathInterpolator,
batteryMeterViewController, batteryMeterViewController,
featureFlags, featureFlags,
@@ -167,62 +136,6 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
assertThat(clock.isClickable).isFalse() assertThat(clock.isClickable).isFalse()
} }
@Test
fun testIgnoredSlotsOnAttached_noIndicators() {
setPrivacyController(micCamera = false, location = false)
controller.init()
verify(iconContainer).removeIgnoredSlot(cameraSlotName)
verify(iconContainer).removeIgnoredSlot(microphoneSlotName)
verify(iconContainer).removeIgnoredSlot(locationSlotName)
}
@Test
fun testIgnoredSlotsOnAttached_onlyMicCamera() {
setPrivacyController(micCamera = true, location = false)
controller.init()
verify(iconContainer).addIgnoredSlot(cameraSlotName)
verify(iconContainer).addIgnoredSlot(microphoneSlotName)
verify(iconContainer).removeIgnoredSlot(locationSlotName)
}
@Test
fun testIgnoredSlotsOnAttached_onlyLocation() {
setPrivacyController(micCamera = false, location = true)
controller.init()
verify(iconContainer).removeIgnoredSlot(cameraSlotName)
verify(iconContainer).removeIgnoredSlot(microphoneSlotName)
verify(iconContainer).addIgnoredSlot(locationSlotName)
}
@Test
fun testIgnoredSlotsOnAttached_locationMicCamera() {
setPrivacyController(micCamera = true, location = true)
controller.init()
verify(iconContainer).addIgnoredSlot(cameraSlotName)
verify(iconContainer).addIgnoredSlot(microphoneSlotName)
verify(iconContainer).addIgnoredSlot(locationSlotName)
}
@Test
fun testPrivacyChipClicked() {
controller.init()
val captor = argumentCaptor<View.OnClickListener>()
verify(privacyChip).setOnClickListener(capture(captor))
captor.value.onClick(privacyChip)
verify(privacyDialogController).showDialog(any(Context::class.java))
}
@Test @Test
fun testSingleCarrierListenerAttachedOnInit() { fun testSingleCarrierListenerAttachedOnInit() {
controller.init() controller.init()
@@ -292,14 +205,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
`when`(view.findViewById<View>(anyInt())).thenReturn(mockView) `when`(view.findViewById<View>(anyInt())).thenReturn(mockView)
`when`(view.findViewById<QSCarrierGroup>(R.id.carrier_group)).thenReturn(qsCarrierGroup) `when`(view.findViewById<QSCarrierGroup>(R.id.carrier_group)).thenReturn(qsCarrierGroup)
`when`(view.findViewById<StatusIconContainer>(R.id.statusIcons)).thenReturn(iconContainer) `when`(view.findViewById<StatusIconContainer>(R.id.statusIcons)).thenReturn(iconContainer)
`when`(view.findViewById<OngoingPrivacyChip>(R.id.privacy_chip)).thenReturn(privacyChip)
`when`(view.findViewById<Clock>(R.id.clock)).thenReturn(clock) `when`(view.findViewById<Clock>(R.id.clock)).thenReturn(clock)
`when`(view.requireViewById<VariableDateView>(R.id.date)).thenReturn(variableDateView) `when`(view.requireViewById<VariableDateView>(R.id.date)).thenReturn(variableDateView)
`when`(view.requireViewById<VariableDateView>(R.id.date_clock)).thenReturn(variableDateView) `when`(view.requireViewById<VariableDateView>(R.id.date_clock)).thenReturn(variableDateView)
} }
private fun setPrivacyController(micCamera: Boolean, location: Boolean) {
`when`(privacyItemController.micCameraAvailable).thenReturn(micCamera)
`when`(privacyItemController.locationAvailable).thenReturn(location)
}
} }

View File

@@ -9,6 +9,7 @@ import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterView
import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.FeatureFlags
import com.android.systemui.qs.HeaderPrivacyIconsController
import com.android.systemui.qs.carrier.QSCarrierGroupController import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Before import org.junit.Before
@@ -34,6 +35,7 @@ class SplitShadeHeaderControllerTest : SysuiTestCase() {
@Mock private lateinit var featureFlags: FeatureFlags @Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var batteryMeterView: BatteryMeterView @Mock private lateinit var batteryMeterView: BatteryMeterView
@Mock private lateinit var batteryMeterViewController: BatteryMeterViewController @Mock private lateinit var batteryMeterViewController: BatteryMeterViewController
@Mock private lateinit var privacyIconsController: HeaderPrivacyIconsController
@JvmField @Rule val mockitoRule = MockitoJUnit.rule() @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
var viewVisibility = View.GONE var viewVisibility = View.GONE
@@ -56,8 +58,14 @@ class SplitShadeHeaderControllerTest : SysuiTestCase() {
} }
whenever(view.visibility).thenAnswer { _ -> viewVisibility } whenever(view.visibility).thenAnswer { _ -> viewVisibility }
whenever(featureFlags.useCombinedQSHeaders()).thenReturn(false) whenever(featureFlags.useCombinedQSHeaders()).thenReturn(false)
splitShadeHeaderController = SplitShadeHeaderController(view, statusBarIconController, splitShadeHeaderController = SplitShadeHeaderController(
qsCarrierGroupControllerBuilder, featureFlags, batteryMeterViewController) view,
statusBarIconController,
privacyIconsController,
qsCarrierGroupControllerBuilder,
featureFlags,
batteryMeterViewController
)
} }
@Test @Test