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