Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 66dfc76f authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Adding privacy indicators to split shade header"

parents a98327f3 a0f94cfe
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -83,6 +83,17 @@
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                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>
    </FrameLayout>

+146 −0
Original line number Diff line number Diff line
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)
        }
    }
}
 No newline at end of file
+0 −1
Original line number Diff line number Diff line
@@ -374,7 +374,6 @@ public class QuickStatusBarHeader extends FrameLayout {
    }

    void setChipVisibility(boolean visibility) {
        mPrivacyChip.setVisibility(visibility ? View.VISIBLE : View.GONE);
        if (visibility) {
            // Animates the icons and battery indicator from alpha 0 to 1, when the chip is visible
            mIconsAlphaAnimator = mIconsAlphaAnimatorFixed;
+13 −135
Original line number Diff line number Diff line
@@ -17,13 +17,8 @@
package com.android.systemui.qs;

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.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -31,13 +26,6 @@ import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
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.dagger.QSScope;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
@@ -55,23 +43,17 @@ import javax.inject.Inject;
 * Controller for {@link QuickStatusBarHeader}.
 */
@QSScope
class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader> {
    private static final String TAG = "QuickStatusBarHeader";
class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader> implements
        ChipVisibilityListener {

    private final PrivacyItemController mPrivacyItemController;
    private final ActivityStarter mActivityStarter;
    private final UiEventLogger mUiEventLogger;
    private final QSCarrierGroupController mQSCarrierGroupController;
    private final QuickQSPanelController mQuickQSPanelController;
    private final OngoingPrivacyChip mPrivacyChip;
    private final Clock mClockView;
    private final StatusBarIconController mStatusBarIconController;
    private final DemoModeController mDemoModeController;
    private final StatusIconContainer mIconContainer;
    private final StatusBarIconController.TintedIconManager mIconManager;
    private final DemoMode mDemoModeReceiver;
    private final PrivacyLogger mPrivacyLogger;
    private final PrivacyDialogController mPrivacyDialogController;
    private final QSExpansionPathInterpolator mQSExpansionPathInterpolator;
    private final FeatureFlags mFeatureFlags;
    private final BatteryMeterViewController mBatteryMeterViewController;
@@ -79,83 +61,31 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader

    private final VariableDateViewController mVariableDateViewControllerDateView;
    private final VariableDateViewController mVariableDateViewControllerClockDateView;
    private final HeaderPrivacyIconsController mPrivacyIconsController;

    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 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
    QuickStatusBarHeaderController(QuickStatusBarHeader view,
            PrivacyItemController privacyItemController,
            ActivityStarter activityStarter, UiEventLogger uiEventLogger,
            HeaderPrivacyIconsController headerPrivacyIconsController,
            StatusBarIconController statusBarIconController,
            DemoModeController demoModeController,
            QuickQSPanelController quickQSPanelController,
            QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder,
            PrivacyLogger privacyLogger,
            SysuiColorExtractor colorExtractor,
            PrivacyDialogController privacyDialogController,
            QSExpansionPathInterpolator qsExpansionPathInterpolator,
            FeatureFlags featureFlags,
            VariableDateViewController.Factory variableDateViewControllerFactory,
            BatteryMeterViewController batteryMeterViewController,
            StatusBarContentInsetsProvider statusBarContentInsetsProvider) {
        super(view);
        mPrivacyItemController = privacyItemController;
        mActivityStarter = activityStarter;
        mUiEventLogger = uiEventLogger;
        mPrivacyIconsController = headerPrivacyIconsController;
        mStatusBarIconController = statusBarIconController;
        mDemoModeController = demoModeController;
        mQuickQSPanelController = quickQSPanelController;
        mPrivacyLogger = privacyLogger;
        mPrivacyDialogController = privacyDialogController;
        mQSExpansionPathInterpolator = qsExpansionPathInterpolator;
        mFeatureFlags = featureFlags;
        mBatteryMeterViewController = batteryMeterViewController;
@@ -164,8 +94,6 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
        mQSCarrierGroupController = qsCarrierGroupControllerBuilder
                .setQSCarrierGroup(mView.findViewById(R.id.carrier_group))
                .build();

        mPrivacyChip = mView.findViewById(R.id.privacy_chip);
        mClockView = mView.findViewById(R.id.clock);
        mIconContainer = mView.findViewById(R.id.statusIcons);
        mVariableDateViewControllerDateView = variableDateViewControllerFactory.create(
@@ -184,10 +112,6 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
        };
        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
        mBatteryMeterViewController.ignoreTunerUpdates();
    }
@@ -199,20 +123,13 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader

    @Override
    protected void onViewAttached() {
        mPrivacyChip.setOnClickListener(mOnClickListener);

        mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
        mLocationIndicatorsEnabled = mPrivacyItemController.getLocationAvailable();

        // Ignore privacy icons because they show in the space above QQS
        updatePrivacyIconSlots();
        mPrivacyIconsController.onParentVisible();
        mPrivacyIconsController.setChipVisibilityListener(this);
        mIconContainer.addIgnoredSlot(
                getResources().getString(com.android.internal.R.string.status_bar_managed_profile));
        mIconContainer.setShouldRestrictIcons(false);
        mStatusBarIconController.addIconGroup(mIconManager);

        setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE);

        mView.setIsSingleCarrier(mQSCarrierGroupController.isSingleCarrier());
        mQSCarrierGroupController
                .setOnSingleCarrierChangedListener(mView::setIsSingleCarrier);
@@ -242,7 +159,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
    @Override
    protected void onViewDetached() {
        mColorExtractor.removeOnColorsChangedListener(mOnColorsChangedListener);
        mPrivacyChip.setOnClickListener(null);
        mPrivacyIconsController.onParentInvisible();
        mStatusBarIconController.removeIconGroup(mIconManager);
        mQSCarrierGroupController.setOnSingleCarrierChangedListener(null);
        mDemoModeController.removeCallback(mDemoModeReceiver);
@@ -267,54 +184,15 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
        }

        if (listening) {
            // Get the most up to date info
            mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
            mLocationIndicatorsEnabled = mPrivacyItemController.getLocationAvailable();
            mPrivacyItemController.addCallback(mPICCallback);
        } else {
            mPrivacyItemController.removeCallback(mPICCallback);
            mPrivacyChipLogged = false;
        }
    }

    private void setChipVisibility(boolean chipVisible) {
        if (chipVisible && getChipEnabled()) {
            mPrivacyLogger.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 (!mPrivacyChipLogged && mListening) {
                mPrivacyChipLogged = true;
                mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW);
            }
            mPrivacyIconsController.startListening();
        } else {
            mPrivacyLogger.logChipVisible(false);
            mPrivacyIconsController.stopListening();
        }
        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;
    @Override
    public void onChipVisibilityRefreshed(boolean visible) {
        mView.setChipVisibility(visible);
    }

    public void setContentMargins(int marginStart, int marginEnd) {
+16 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import com.android.systemui.R;
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.FooterActionsController;
import com.android.systemui.qs.FooterActionsController.ExpansionState;
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.QuickStatusBarHeader;
import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.statusbar.phone.StatusIconContainer;

import javax.inject.Named;

@@ -189,4 +191,18 @@ public interface QSFragmentModule {
    static boolean providesQSUsingMediaPlayer(Context 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);
    }
}
 No newline at end of file
Loading