1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.qs; 18 19 import android.os.Bundle; 20 import android.view.View; 21 import android.view.View.OnClickListener; 22 23 import androidx.annotation.NonNull; 24 25 import com.android.internal.colorextraction.ColorExtractor; 26 import com.android.internal.logging.UiEventLogger; 27 import com.android.systemui.R; 28 import com.android.systemui.colorextraction.SysuiColorExtractor; 29 import com.android.systemui.demomode.DemoMode; 30 import com.android.systemui.demomode.DemoModeController; 31 import com.android.systemui.plugins.ActivityStarter; 32 import com.android.systemui.privacy.OngoingPrivacyChip; 33 import com.android.systemui.privacy.PrivacyChipEvent; 34 import com.android.systemui.privacy.PrivacyDialogController; 35 import com.android.systemui.privacy.PrivacyItem; 36 import com.android.systemui.privacy.PrivacyItemController; 37 import com.android.systemui.privacy.logging.PrivacyLogger; 38 import com.android.systemui.qs.carrier.QSCarrierGroupController; 39 import com.android.systemui.qs.dagger.QSScope; 40 import com.android.systemui.statusbar.FeatureFlags; 41 import com.android.systemui.statusbar.phone.StatusBarIconController; 42 import com.android.systemui.statusbar.phone.StatusIconContainer; 43 import com.android.systemui.statusbar.policy.Clock; 44 import com.android.systemui.util.ViewController; 45 46 import java.util.List; 47 48 import javax.inject.Inject; 49 50 /** 51 * Controller for {@link QuickStatusBarHeader}. 52 */ 53 @QSScope 54 class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader> { 55 private static final String TAG = "QuickStatusBarHeader"; 56 57 private final PrivacyItemController mPrivacyItemController; 58 private final ActivityStarter mActivityStarter; 59 private final UiEventLogger mUiEventLogger; 60 private final QSCarrierGroupController mQSCarrierGroupController; 61 private final QuickQSPanelController mHeaderQsPanelController; 62 private final OngoingPrivacyChip mPrivacyChip; 63 private final Clock mClockView; 64 private final StatusBarIconController mStatusBarIconController; 65 private final DemoModeController mDemoModeController; 66 private final StatusIconContainer mIconContainer; 67 private final StatusBarIconController.TintedIconManager mIconManager; 68 private final DemoMode mDemoModeReceiver; 69 private final PrivacyLogger mPrivacyLogger; 70 private final PrivacyDialogController mPrivacyDialogController; 71 private final QSExpansionPathInterpolator mQSExpansionPathInterpolator; 72 private final FeatureFlags mFeatureFlags; 73 74 private boolean mListening; 75 private boolean mMicCameraIndicatorsEnabled; 76 private boolean mLocationIndicatorsEnabled; 77 private boolean mPrivacyChipLogged; 78 private final String mCameraSlot; 79 private final String mMicSlot; 80 private final String mLocationSlot; 81 82 private SysuiColorExtractor mColorExtractor; 83 private ColorExtractor.OnColorsChangedListener mOnColorsChangedListener; 84 85 private PrivacyItemController.Callback mPICCallback = new PrivacyItemController.Callback() { 86 @Override 87 public void onPrivacyItemsChanged(@NonNull List<PrivacyItem> privacyItems) { 88 mPrivacyChip.setPrivacyList(privacyItems); 89 setChipVisibility(!privacyItems.isEmpty()); 90 } 91 92 @Override 93 public void onFlagMicCameraChanged(boolean flag) { 94 if (mMicCameraIndicatorsEnabled != flag) { 95 mMicCameraIndicatorsEnabled = flag; 96 update(); 97 } 98 } 99 100 @Override 101 public void onFlagLocationChanged(boolean flag) { 102 if (mLocationIndicatorsEnabled != flag) { 103 mLocationIndicatorsEnabled = flag; 104 update(); 105 } 106 } 107 108 private void update() { 109 updatePrivacyIconSlots(); 110 setChipVisibility(!mPrivacyChip.getPrivacyList().isEmpty()); 111 } 112 }; 113 114 private View.OnClickListener mOnClickListener = new OnClickListener() { 115 @Override 116 public void onClick(View v) { 117 if (v == mPrivacyChip) { 118 // If the privacy chip is visible, it means there were some indicators 119 mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK); 120 mPrivacyDialogController.showDialog(getContext()); 121 } 122 } 123 }; 124 125 @Inject QuickStatusBarHeaderController(QuickStatusBarHeader view, PrivacyItemController privacyItemController, ActivityStarter activityStarter, UiEventLogger uiEventLogger, StatusBarIconController statusBarIconController, DemoModeController demoModeController, QuickQSPanelController quickQSPanelController, QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder, PrivacyLogger privacyLogger, SysuiColorExtractor colorExtractor, PrivacyDialogController privacyDialogController, QSExpansionPathInterpolator qsExpansionPathInterpolator, FeatureFlags featureFlags)126 QuickStatusBarHeaderController(QuickStatusBarHeader view, 127 PrivacyItemController privacyItemController, 128 ActivityStarter activityStarter, UiEventLogger uiEventLogger, 129 StatusBarIconController statusBarIconController, 130 DemoModeController demoModeController, 131 QuickQSPanelController quickQSPanelController, 132 QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder, 133 PrivacyLogger privacyLogger, 134 SysuiColorExtractor colorExtractor, 135 PrivacyDialogController privacyDialogController, 136 QSExpansionPathInterpolator qsExpansionPathInterpolator, 137 FeatureFlags featureFlags) { 138 super(view); 139 mPrivacyItemController = privacyItemController; 140 mActivityStarter = activityStarter; 141 mUiEventLogger = uiEventLogger; 142 mStatusBarIconController = statusBarIconController; 143 mDemoModeController = demoModeController; 144 mHeaderQsPanelController = quickQSPanelController; 145 mPrivacyLogger = privacyLogger; 146 mPrivacyDialogController = privacyDialogController; 147 mQSExpansionPathInterpolator = qsExpansionPathInterpolator; 148 mFeatureFlags = featureFlags; 149 150 mQSCarrierGroupController = qsCarrierGroupControllerBuilder 151 .setQSCarrierGroup(mView.findViewById(R.id.carrier_group)) 152 .build(); 153 154 mPrivacyChip = mView.findViewById(R.id.privacy_chip); 155 mClockView = mView.findViewById(R.id.clock); 156 mIconContainer = mView.findViewById(R.id.statusIcons); 157 158 mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer, featureFlags); 159 mDemoModeReceiver = new ClockDemoModeReceiver(mClockView); 160 mColorExtractor = colorExtractor; 161 mOnColorsChangedListener = (extractor, which) -> { 162 final boolean lightTheme = mColorExtractor.getNeutralColors().supportsDarkText(); 163 mClockView.onColorsChanged(lightTheme); 164 }; 165 mColorExtractor.addOnColorsChangedListener(mOnColorsChangedListener); 166 167 mCameraSlot = getResources().getString(com.android.internal.R.string.status_bar_camera); 168 mMicSlot = getResources().getString(com.android.internal.R.string.status_bar_microphone); 169 mLocationSlot = getResources().getString(com.android.internal.R.string.status_bar_location); 170 } 171 172 @Override onViewAttached()173 protected void onViewAttached() { 174 mPrivacyChip.setOnClickListener(mOnClickListener); 175 176 mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable(); 177 mLocationIndicatorsEnabled = mPrivacyItemController.getLocationAvailable(); 178 179 // Ignore privacy icons because they show in the space above QQS 180 updatePrivacyIconSlots(); 181 mIconContainer.addIgnoredSlot( 182 getResources().getString(com.android.internal.R.string.status_bar_managed_profile)); 183 mIconContainer.setShouldRestrictIcons(false); 184 mStatusBarIconController.addIconGroup(mIconManager); 185 186 setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE); 187 188 mView.setIsSingleCarrier(mQSCarrierGroupController.isSingleCarrier()); 189 mQSCarrierGroupController 190 .setOnSingleCarrierChangedListener(mView::setIsSingleCarrier); 191 192 List<String> rssiIgnoredSlots; 193 194 if (mFeatureFlags.isCombinedStatusBarSignalIconsEnabled()) { 195 rssiIgnoredSlots = List.of( 196 getResources().getString(com.android.internal.R.string.status_bar_no_calling), 197 getResources().getString(com.android.internal.R.string.status_bar_call_strength) 198 ); 199 } else { 200 rssiIgnoredSlots = List.of( 201 getResources().getString(com.android.internal.R.string.status_bar_mobile) 202 ); 203 } 204 205 mView.onAttach(mIconManager, mQSExpansionPathInterpolator, rssiIgnoredSlots); 206 207 mDemoModeController.addCallback(mDemoModeReceiver); 208 } 209 210 @Override onViewDetached()211 protected void onViewDetached() { 212 mColorExtractor.removeOnColorsChangedListener(mOnColorsChangedListener); 213 mPrivacyChip.setOnClickListener(null); 214 mStatusBarIconController.removeIconGroup(mIconManager); 215 mQSCarrierGroupController.setOnSingleCarrierChangedListener(null); 216 mDemoModeController.removeCallback(mDemoModeReceiver); 217 setListening(false); 218 } 219 setListening(boolean listening)220 public void setListening(boolean listening) { 221 mQSCarrierGroupController.setListening(listening); 222 223 if (listening == mListening) { 224 return; 225 } 226 mListening = listening; 227 228 mHeaderQsPanelController.setListening(listening); 229 if (mHeaderQsPanelController.isListening()) { 230 mHeaderQsPanelController.refreshAllTiles(); 231 } 232 233 if (mHeaderQsPanelController.switchTileLayout(false)) { 234 mView.updateResources(); 235 } 236 237 if (listening) { 238 // Get the most up to date info 239 mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable(); 240 mLocationIndicatorsEnabled = mPrivacyItemController.getLocationAvailable(); 241 mPrivacyItemController.addCallback(mPICCallback); 242 } else { 243 mPrivacyItemController.removeCallback(mPICCallback); 244 mPrivacyChipLogged = false; 245 } 246 } 247 setChipVisibility(boolean chipVisible)248 private void setChipVisibility(boolean chipVisible) { 249 if (chipVisible && getChipEnabled()) { 250 mPrivacyLogger.logChipVisible(true); 251 // Makes sure that the chip is logged as viewed at most once each time QS is opened 252 // mListening makes sure that the callback didn't return after the user closed QS 253 if (!mPrivacyChipLogged && mListening) { 254 mPrivacyChipLogged = true; 255 mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW); 256 } 257 } else { 258 mPrivacyLogger.logChipVisible(false); 259 } 260 mView.setChipVisibility(chipVisible); 261 } 262 updatePrivacyIconSlots()263 private void updatePrivacyIconSlots() { 264 if (getChipEnabled()) { 265 if (mMicCameraIndicatorsEnabled) { 266 mIconContainer.addIgnoredSlot(mCameraSlot); 267 mIconContainer.addIgnoredSlot(mMicSlot); 268 } else { 269 mIconContainer.removeIgnoredSlot(mCameraSlot); 270 mIconContainer.removeIgnoredSlot(mMicSlot); 271 } 272 if (mLocationIndicatorsEnabled) { 273 mIconContainer.addIgnoredSlot(mLocationSlot); 274 } else { 275 mIconContainer.removeIgnoredSlot(mLocationSlot); 276 } 277 } else { 278 mIconContainer.removeIgnoredSlot(mCameraSlot); 279 mIconContainer.removeIgnoredSlot(mMicSlot); 280 mIconContainer.removeIgnoredSlot(mLocationSlot); 281 } 282 } 283 getChipEnabled()284 private boolean getChipEnabled() { 285 return mMicCameraIndicatorsEnabled || mLocationIndicatorsEnabled; 286 } 287 setContentMargins(int marginStart, int marginEnd)288 public void setContentMargins(int marginStart, int marginEnd) { 289 mHeaderQsPanelController.setContentMargins(marginStart, marginEnd); 290 } 291 292 private static class ClockDemoModeReceiver implements DemoMode { 293 private Clock mClockView; 294 295 @Override demoCommands()296 public List<String> demoCommands() { 297 return List.of(COMMAND_CLOCK); 298 } 299 ClockDemoModeReceiver(Clock clockView)300 ClockDemoModeReceiver(Clock clockView) { 301 mClockView = clockView; 302 } 303 304 @Override dispatchDemoCommand(String command, Bundle args)305 public void dispatchDemoCommand(String command, Bundle args) { 306 mClockView.dispatchDemoCommand(command, args); 307 } 308 309 @Override onDemoModeStarted()310 public void onDemoModeStarted() { 311 mClockView.onDemoModeStarted(); 312 } 313 314 @Override onDemoModeFinished()315 public void onDemoModeFinished() { 316 mClockView.onDemoModeFinished(); 317 } 318 } 319 } 320