1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.systemui.statusbar.phone; 16 17 import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE; 18 19 import android.annotation.Nullable; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.database.ContentObserver; 24 import android.hardware.display.ColorDisplayManager; 25 import android.hardware.display.NightDisplayListener; 26 import android.os.Handler; 27 import android.os.UserHandle; 28 import android.util.Log; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.systemui.dagger.NightDisplayListenerModule; 32 import com.android.systemui.dagger.qualifiers.Background; 33 import com.android.systemui.plugins.qs.QSTile; 34 import com.android.systemui.qs.AutoAddTracker; 35 import com.android.systemui.qs.QSHost; 36 import com.android.systemui.qs.ReduceBrightColorsController; 37 import com.android.systemui.qs.UserSettingObserver; 38 import com.android.systemui.qs.external.CustomTile; 39 import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository; 40 import com.android.systemui.res.R; 41 import com.android.systemui.statusbar.policy.CastController; 42 import com.android.systemui.statusbar.policy.CastController.CastDevice; 43 import com.android.systemui.statusbar.policy.DataSaverController; 44 import com.android.systemui.statusbar.policy.DataSaverController.Listener; 45 import com.android.systemui.statusbar.policy.DeviceControlsController; 46 import com.android.systemui.statusbar.policy.HotspotController; 47 import com.android.systemui.statusbar.policy.HotspotController.Callback; 48 import com.android.systemui.statusbar.policy.SafetyController; 49 import com.android.systemui.statusbar.policy.WalletController; 50 import com.android.systemui.util.UserAwareController; 51 import com.android.systemui.util.settings.SecureSettings; 52 53 import java.util.ArrayList; 54 import java.util.Collection; 55 import java.util.Objects; 56 57 import javax.inject.Named; 58 59 /** 60 * Manages which tiles should be automatically added to QS. 61 */ 62 public class AutoTileManager implements UserAwareController { 63 private static final String TAG = "AutoTileManager"; 64 65 public static final String HOTSPOT = "hotspot"; 66 public static final String SAVER = "saver"; 67 public static final String INVERSION = "inversion"; 68 public static final String WORK = "work"; 69 public static final String NIGHT = "night"; 70 public static final String CAST = "cast"; 71 public static final String DEVICE_CONTROLS = "controls"; 72 public static final String WALLET = "wallet"; 73 public static final String BRIGHTNESS = "reduce_brightness"; 74 static final String SETTING_SEPARATOR = ":"; 75 76 private UserHandle mCurrentUser; 77 private boolean mInitialized; 78 private final String mSafetySpec; 79 80 protected final Context mContext; 81 protected final QSHost mHost; 82 protected final Handler mHandler; 83 protected final SecureSettings mSecureSettings; 84 protected final AutoAddTracker mAutoTracker; 85 private final HotspotController mHotspotController; 86 private final DataSaverController mDataSaverController; 87 private final ManagedProfileController mManagedProfileController; 88 private final NightDisplayListenerModule.Builder mNightDisplayListenerBuilder; 89 private NightDisplayListener mNightDisplayListener; 90 private final CastController mCastController; 91 private final DeviceControlsController mDeviceControlsController; 92 private final WalletController mWalletController; 93 private final ReduceBrightColorsController mReduceBrightColorsController; 94 private final SafetyController mSafetyController; 95 private final boolean mIsReduceBrightColorsAvailable; 96 private final ArrayList<AutoAddSetting> mAutoAddSettingList = new ArrayList<>(); 97 AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder, QSHost host, @Background Handler handler, SecureSettings secureSettings, HotspotController hotspotController, DataSaverController dataSaverController, ManagedProfileController managedProfileController, NightDisplayListenerModule.Builder nightDisplayListenerBuilder, CastController castController, ReduceBrightColorsController reduceBrightColorsController, DeviceControlsController deviceControlsController, WalletController walletController, SafetyController safetyController, @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable)98 public AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder, 99 QSHost host, 100 @Background Handler handler, 101 SecureSettings secureSettings, 102 HotspotController hotspotController, 103 DataSaverController dataSaverController, 104 ManagedProfileController managedProfileController, 105 NightDisplayListenerModule.Builder nightDisplayListenerBuilder, 106 CastController castController, 107 ReduceBrightColorsController reduceBrightColorsController, 108 DeviceControlsController deviceControlsController, 109 WalletController walletController, 110 SafetyController safetyController, 111 @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) { 112 mContext = context; 113 mHost = host; 114 mSecureSettings = secureSettings; 115 mCurrentUser = mHost.getUserContext().getUser(); 116 mAutoTracker = autoAddTrackerBuilder.setUserId(mCurrentUser.getIdentifier()).build(); 117 mHandler = handler; 118 mHotspotController = hotspotController; 119 mDataSaverController = dataSaverController; 120 mManagedProfileController = managedProfileController; 121 mNightDisplayListenerBuilder = nightDisplayListenerBuilder; 122 mCastController = castController; 123 mReduceBrightColorsController = reduceBrightColorsController; 124 mIsReduceBrightColorsAvailable = isReduceBrightColorsAvailable; 125 mDeviceControlsController = deviceControlsController; 126 mWalletController = walletController; 127 mSafetyController = safetyController; 128 String safetySpecClass; 129 try { 130 safetySpecClass = 131 context.getResources().getString(R.string.safety_quick_settings_tile_class); 132 if (safetySpecClass.length() == 0) { 133 safetySpecClass = null; 134 } 135 } catch (Resources.NotFoundException | NullPointerException e) { 136 safetySpecClass = null; 137 } 138 mSafetySpec = safetySpecClass != null ? CustomTile.toSpec(new ComponentName(mContext 139 .getPackageManager().getPermissionControllerPackageName(), safetySpecClass)) : null; 140 } 141 142 /** 143 * Init method must be called after construction to start listening 144 */ init()145 public void init() { 146 QSPipelineFlagsRepository.Utils.assertInLegacyMode(); 147 if (mInitialized) { 148 Log.w(TAG, "Trying to re-initialize"); 149 return; 150 } 151 mAutoTracker.initialize(); 152 populateSettingsList(); 153 startControllersAndSettingsListeners(); 154 mInitialized = true; 155 } 156 startControllersAndSettingsListeners()157 protected void startControllersAndSettingsListeners() { 158 if (!mAutoTracker.isAdded(HOTSPOT)) { 159 mHotspotController.addCallback(mHotspotCallback); 160 } 161 if (!mAutoTracker.isAdded(SAVER)) { 162 mDataSaverController.addCallback(mDataSaverListener); 163 } 164 mManagedProfileController.addCallback(mProfileCallback); 165 166 mNightDisplayListener = mNightDisplayListenerBuilder 167 .setUser(mCurrentUser.getIdentifier()) 168 .build(); 169 if (!mAutoTracker.isAdded(NIGHT) 170 && ColorDisplayManager.isNightDisplayAvailable(mContext)) { 171 mNightDisplayListener.setCallback(mNightDisplayCallback); 172 } 173 if (!mAutoTracker.isAdded(CAST)) { 174 mCastController.addCallback(mCastCallback); 175 } 176 if (!mAutoTracker.isAdded(BRIGHTNESS) && mIsReduceBrightColorsAvailable) { 177 mReduceBrightColorsController.addCallback(mReduceBrightColorsCallback); 178 } 179 // We always want this callback, because if the feature stops being supported, 180 // we want to remove the tile from AutoAddTracker. That way it will be re-added when the 181 // feature is reenabled (similar to work tile). 182 mDeviceControlsController.setCallback(mDeviceControlsCallback); 183 if (!mAutoTracker.isAdded(WALLET)) { 184 initWalletController(); 185 } 186 if (mSafetySpec != null) { 187 if (!mAutoTracker.isAdded(mSafetySpec)) { 188 initSafetyTile(); 189 } 190 mSafetyController.addCallback(mSafetyCallback); 191 } 192 193 int settingsN = mAutoAddSettingList.size(); 194 for (int i = 0; i < settingsN; i++) { 195 if (!mAutoTracker.isAdded(mAutoAddSettingList.get(i).mSpec)) { 196 mAutoAddSettingList.get(i).setListening(true); 197 } 198 } 199 } 200 stopListening()201 protected void stopListening() { 202 mHotspotController.removeCallback(mHotspotCallback); 203 mDataSaverController.removeCallback(mDataSaverListener); 204 mManagedProfileController.removeCallback(mProfileCallback); 205 if (ColorDisplayManager.isNightDisplayAvailable(mContext) 206 && mNightDisplayListener != null) { 207 mNightDisplayListener.setCallback(null); 208 } 209 if (mIsReduceBrightColorsAvailable) { 210 mReduceBrightColorsController.removeCallback(mReduceBrightColorsCallback); 211 } 212 mCastController.removeCallback(mCastCallback); 213 mDeviceControlsController.removeCallback(); 214 if (mSafetySpec != null) { 215 mSafetyController.removeCallback(mSafetyCallback); 216 } 217 int settingsN = mAutoAddSettingList.size(); 218 for (int i = 0; i < settingsN; i++) { 219 mAutoAddSettingList.get(i).setListening(false); 220 } 221 } 222 destroy()223 public void destroy() { 224 stopListening(); 225 mAutoTracker.destroy(); 226 } 227 228 /** 229 * Populates a list with the pairs setting:spec in the config resource. 230 * <p> 231 * This will only create {@link AutoAddSetting} objects for those tiles that have not been 232 * auto-added before, and set the corresponding {@link ContentObserver} to listening. 233 */ populateSettingsList()234 private void populateSettingsList() { 235 String [] autoAddList; 236 try { 237 autoAddList = mContext.getResources().getStringArray( 238 R.array.config_quickSettingsAutoAdd); 239 } catch (Resources.NotFoundException e) { 240 Log.w(TAG, "Missing config resource"); 241 return; 242 } 243 // getStringArray returns @NotNull, so if we got here, autoAddList is not null 244 for (String tile : autoAddList) { 245 String[] split = tile.split(SETTING_SEPARATOR); 246 if (split.length == 2) { 247 String setting = split[0]; 248 String spec = split[1]; 249 // Populate all the settings. As they may not have been added in other users 250 AutoAddSetting s = new AutoAddSetting( 251 mSecureSettings, mHandler, setting, mCurrentUser.getIdentifier(), spec); 252 mAutoAddSettingList.add(s); 253 } else { 254 Log.w(TAG, "Malformed item in array: " + tile); 255 } 256 } 257 } 258 259 /* 260 * This will be sent off the main thread if needed 261 */ 262 @Override changeUser(UserHandle newUser)263 public void changeUser(UserHandle newUser) { 264 if (!mInitialized) { 265 throw new IllegalStateException("AutoTileManager not initialized"); 266 } 267 if (!Thread.currentThread().equals(mHandler.getLooper().getThread())) { 268 mHandler.post(() -> changeUser(newUser)); 269 return; 270 } 271 if (newUser.getIdentifier() == mCurrentUser.getIdentifier()) { 272 return; 273 } 274 stopListening(); 275 mCurrentUser = newUser; 276 int settingsN = mAutoAddSettingList.size(); 277 for (int i = 0; i < settingsN; i++) { 278 mAutoAddSettingList.get(i).setUserId(newUser.getIdentifier()); 279 } 280 mAutoTracker.changeUser(newUser); 281 startControllersAndSettingsListeners(); 282 } 283 284 @Override getCurrentUserId()285 public int getCurrentUserId() { 286 return mCurrentUser.getIdentifier(); 287 } 288 289 private final ManagedProfileController.Callback mProfileCallback = 290 new ManagedProfileController.Callback() { 291 @Override 292 public void onManagedProfileChanged() { 293 if (mManagedProfileController.hasActiveProfile()) { 294 if (mAutoTracker.isAdded(WORK)) return; 295 final int position = mAutoTracker.getRestoredTilePosition(WORK); 296 mHost.addTile(WORK, position); 297 mAutoTracker.setTileAdded(WORK); 298 } else { 299 if (!mAutoTracker.isAdded(WORK)) return; 300 mHost.removeTile(WORK); 301 mAutoTracker.setTileRemoved(WORK); 302 } 303 } 304 305 @Override 306 public void onManagedProfileRemoved() { 307 } 308 }; 309 310 private final DataSaverController.Listener mDataSaverListener = new Listener() { 311 @Override 312 public void onDataSaverChanged(boolean isDataSaving) { 313 if (mAutoTracker.isAdded(SAVER)) return; 314 if (isDataSaving) { 315 mHost.addTile(SAVER); 316 mAutoTracker.setTileAdded(SAVER); 317 mHandler.post(() -> mDataSaverController.removeCallback(mDataSaverListener)); 318 } 319 } 320 }; 321 322 private final HotspotController.Callback mHotspotCallback = new Callback() { 323 @Override 324 public void onHotspotChanged(boolean enabled, int numDevices) { 325 if (mAutoTracker.isAdded(HOTSPOT)) return; 326 if (enabled) { 327 mHost.addTile(HOTSPOT); 328 mAutoTracker.setTileAdded(HOTSPOT); 329 mHandler.post(() -> mHotspotController.removeCallback(mHotspotCallback)); 330 } 331 } 332 }; 333 334 private final DeviceControlsController.Callback mDeviceControlsCallback = 335 new DeviceControlsController.Callback() { 336 @Override 337 public void onControlsUpdate(@Nullable Integer position) { 338 if (mAutoTracker.isAdded(DEVICE_CONTROLS)) return; 339 if (position != null && !hasTile(DEVICE_CONTROLS)) { 340 mHost.addTile(DEVICE_CONTROLS, position); 341 mAutoTracker.setTileAdded(DEVICE_CONTROLS); 342 } 343 mHandler.post(() -> mDeviceControlsController.removeCallback()); 344 } 345 346 @Override 347 public void removeControlsAutoTracker() { 348 mAutoTracker.setTileRemoved(DEVICE_CONTROLS); 349 } 350 }; 351 hasTile(String tileSpec)352 private boolean hasTile(String tileSpec) { 353 if (tileSpec == null) return false; 354 Collection<QSTile> tiles = mHost.getTiles(); 355 for (QSTile tile : tiles) { 356 if (tileSpec.equals(tile.getTileSpec())) { 357 return true; 358 } 359 } 360 return false; 361 } 362 initWalletController()363 private void initWalletController() { 364 if (mAutoTracker.isAdded(WALLET)) return; 365 Integer position = mWalletController.getWalletPosition(); 366 367 if (position != null) { 368 mHost.addTile(WALLET, position); 369 mAutoTracker.setTileAdded(WALLET); 370 } 371 } 372 initSafetyTile()373 private void initSafetyTile() { 374 if (mSafetySpec == null || mAutoTracker.isAdded(mSafetySpec)) { 375 return; 376 } 377 mHost.addTile(CustomTile.getComponentFromSpec(mSafetySpec), true); 378 mAutoTracker.setTileAdded(mSafetySpec); 379 } 380 381 @VisibleForTesting 382 final NightDisplayListener.Callback mNightDisplayCallback = 383 new NightDisplayListener.Callback() { 384 @Override 385 public void onActivated(boolean activated) { 386 if (activated) { 387 addNightTile(); 388 } 389 } 390 391 @Override 392 public void onAutoModeChanged(int autoMode) { 393 if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME 394 || autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) { 395 addNightTile(); 396 } 397 } 398 399 private void addNightTile() { 400 if (mAutoTracker.isAdded(NIGHT)) return; 401 mHost.addTile(NIGHT); 402 mAutoTracker.setTileAdded(NIGHT); 403 mHandler.post(() -> mNightDisplayListener.setCallback(null)); 404 } 405 }; 406 407 @VisibleForTesting 408 final ReduceBrightColorsController.Listener mReduceBrightColorsCallback = 409 new ReduceBrightColorsController.Listener() { 410 @Override 411 public void onActivated(boolean activated) { 412 if (activated) { 413 addReduceBrightColorsTile(); 414 } 415 } 416 417 private void addReduceBrightColorsTile() { 418 if (mAutoTracker.isAdded(BRIGHTNESS)) return; 419 mHost.addTile(BRIGHTNESS); 420 mAutoTracker.setTileAdded(BRIGHTNESS); 421 mHandler.post(() -> mReduceBrightColorsController.removeCallback(this)); 422 } 423 }; 424 425 @VisibleForTesting 426 final CastController.Callback mCastCallback = new CastController.Callback() { 427 @Override 428 public void onCastDevicesChanged() { 429 if (mAutoTracker.isAdded(CAST)) return; 430 431 boolean isCasting = false; 432 for (CastDevice device : mCastController.getCastDevices()) { 433 if (device.state == CastDevice.STATE_CONNECTED 434 || device.state == CastDevice.STATE_CONNECTING) { 435 isCasting = true; 436 break; 437 } 438 } 439 440 if (isCasting) { 441 mHost.addTile(CAST); 442 mAutoTracker.setTileAdded(CAST); 443 mHandler.post(() -> mCastController.removeCallback(mCastCallback)); 444 } 445 } 446 }; 447 448 @VisibleForTesting 449 final SafetyController.Listener mSafetyCallback = new SafetyController.Listener() { 450 @Override 451 public void onSafetyCenterEnableChanged(boolean isSafetyCenterEnabled) { 452 if (mSafetySpec == null) { 453 return; 454 } 455 456 if (isSafetyCenterEnabled && !mAutoTracker.isAdded(mSafetySpec)) { 457 initSafetyTile(); 458 } else if (!isSafetyCenterEnabled && mAutoTracker.isAdded(mSafetySpec)) { 459 mHost.removeTile(mSafetySpec); 460 mAutoTracker.setTileRemoved(mSafetySpec); 461 } 462 } 463 }; 464 465 @VisibleForTesting getSecureSettingForKey(String key)466 protected UserSettingObserver getSecureSettingForKey(String key) { 467 for (UserSettingObserver s : mAutoAddSettingList) { 468 if (Objects.equals(key, s.getKey())) { 469 return s; 470 } 471 } 472 return null; 473 } 474 475 /** 476 * Tracks tiles that should be auto added when a setting changes. 477 * <p> 478 * When the setting changes to a value different from 0, if the tile has not been auto added 479 * before, it will be added and the listener will be stopped. 480 */ 481 private class AutoAddSetting extends UserSettingObserver { 482 private final String mSpec; 483 AutoAddSetting( SecureSettings secureSettings, Handler handler, String setting, int userId, String tileSpec )484 AutoAddSetting( 485 SecureSettings secureSettings, 486 Handler handler, 487 String setting, 488 int userId, 489 String tileSpec 490 ) { 491 super(secureSettings, handler, setting, userId); 492 mSpec = tileSpec; 493 } 494 495 @Override handleValueChanged(int value, boolean observedChange)496 protected void handleValueChanged(int value, boolean observedChange) { 497 if (mAutoTracker.isAdded(mSpec)) { 498 // This should not be listening anymore 499 mHandler.post(() -> setListening(false)); 500 return; 501 } 502 if (value != 0) { 503 if (mSpec.startsWith(CustomTile.PREFIX)) { 504 mHost.addTile(CustomTile.getComponentFromSpec(mSpec), /* end */ true); 505 } else { 506 mHost.addTile(mSpec); 507 } 508 mAutoTracker.setTileAdded(mSpec); 509 mHandler.post(() -> setListening(false)); 510 } 511 } 512 } 513 } 514