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