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