1 /* 2 * Copyright (C) 2017 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.qs; 16 17 import android.content.ComponentName; 18 import android.content.Context; 19 import android.content.Intent; 20 import android.content.res.Resources; 21 import android.os.UserHandle; 22 import android.os.UserManager; 23 import android.provider.Settings.Secure; 24 import android.text.TextUtils; 25 import android.util.ArraySet; 26 import android.util.Log; 27 28 import androidx.annotation.MainThread; 29 import androidx.annotation.Nullable; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.systemui.Dumpable; 33 import com.android.systemui.ProtoDumpable; 34 import com.android.systemui.dagger.SysUISingleton; 35 import com.android.systemui.dagger.qualifiers.Main; 36 import com.android.systemui.dump.nano.SystemUIProtoDump; 37 import com.android.systemui.plugins.PluginListener; 38 import com.android.systemui.plugins.PluginManager; 39 import com.android.systemui.plugins.qs.QSFactory; 40 import com.android.systemui.plugins.qs.QSTile; 41 import com.android.systemui.qs.external.CustomTile; 42 import com.android.systemui.qs.external.CustomTileStatePersister; 43 import com.android.systemui.qs.external.TileLifecycleManager; 44 import com.android.systemui.qs.external.TileServiceKey; 45 import com.android.systemui.qs.logging.QSLogger; 46 import com.android.systemui.qs.nano.QsTileState; 47 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository; 48 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; 49 import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository; 50 import com.android.systemui.qs.tiles.di.NewQSTileFactory; 51 import com.android.systemui.res.R; 52 import com.android.systemui.settings.UserFileManager; 53 import com.android.systemui.settings.UserTracker; 54 import com.android.systemui.shade.ShadeController; 55 import com.android.systemui.statusbar.phone.AutoTileManager; 56 import com.android.systemui.tuner.TunerService; 57 import com.android.systemui.tuner.TunerService.Tunable; 58 import com.android.systemui.util.settings.SecureSettings; 59 60 import dagger.Lazy; 61 62 import org.jetbrains.annotations.NotNull; 63 64 import java.io.PrintWriter; 65 import java.util.ArrayList; 66 import java.util.Collection; 67 import java.util.LinkedHashMap; 68 import java.util.List; 69 import java.util.Objects; 70 import java.util.Set; 71 import java.util.concurrent.Executor; 72 import java.util.function.Predicate; 73 import java.util.stream.Collectors; 74 75 import javax.inject.Inject; 76 import javax.inject.Provider; 77 78 /** Platform implementation of the quick settings tile host 79 * 80 * This class keeps track of the set of current tiles and is the in memory source of truth 81 * (ground truth is kept in {@link Secure#QS_TILES}). When the ground truth changes, 82 * {@link #onTuningChanged} will be called and the tiles will be re-created as needed. 83 * 84 * This class also provides the interface for adding/removing/changing tiles. 85 */ 86 @SysUISingleton 87 public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable, 88 PanelInteractor, CustomTileAddedRepository { 89 private static final String TAG = "QSTileHost"; 90 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 91 92 // Shared prefs that hold tile lifecycle info. 93 @VisibleForTesting 94 static final String TILES = "tiles_prefs"; 95 96 private final Context mContext; 97 private final LinkedHashMap<String, QSTile> mTiles = new LinkedHashMap<>(); 98 private final ArrayList<String> mTileSpecs = new ArrayList<>(); 99 private final TunerService mTunerService; 100 private final PluginManager mPluginManager; 101 private final QSLogger mQSLogger; 102 private final CustomTileStatePersister mCustomTileStatePersister; 103 private final Executor mMainExecutor; 104 private final UserFileManager mUserFileManager; 105 106 private final List<Callback> mCallbacks = new ArrayList<>(); 107 @Nullable 108 private AutoTileManager mAutoTiles; 109 private final ArrayList<QSFactory> mQsFactories = new ArrayList<>(); 110 private int mCurrentUser; 111 private final Lazy<ShadeController> mShadeControllerProvider; 112 private Context mUserContext; 113 private UserTracker mUserTracker; 114 private SecureSettings mSecureSettings; 115 // Keep track of whether mTilesList contains the same information as the Settings value. 116 // This is a performance optimization to reduce the number of blocking calls to Settings from 117 // main thread. 118 // This is enforced by only cleaning the flag at the end of a successful run of #onTuningChanged 119 private boolean mTilesListDirty = true; 120 121 private TileLifecycleManager.Factory mTileLifeCycleManagerFactory; 122 123 private final QSPipelineFlagsRepository mFeatureFlags; 124 125 @Inject QSTileHost(Context context, Lazy<NewQSTileFactory> newQsTileFactoryProvider, QSFactory defaultFactory, @Main Executor mainExecutor, PluginManager pluginManager, TunerService tunerService, Provider<AutoTileManager> autoTiles, Lazy<ShadeController> shadeControllerProvider, QSLogger qsLogger, UserTracker userTracker, SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister, TileLifecycleManager.Factory tileLifecycleManagerFactory, UserFileManager userFileManager, QSPipelineFlagsRepository featureFlags )126 public QSTileHost(Context context, 127 Lazy<NewQSTileFactory> newQsTileFactoryProvider, 128 QSFactory defaultFactory, 129 @Main Executor mainExecutor, 130 PluginManager pluginManager, 131 TunerService tunerService, 132 Provider<AutoTileManager> autoTiles, 133 Lazy<ShadeController> shadeControllerProvider, 134 QSLogger qsLogger, 135 UserTracker userTracker, 136 SecureSettings secureSettings, 137 CustomTileStatePersister customTileStatePersister, 138 TileLifecycleManager.Factory tileLifecycleManagerFactory, 139 UserFileManager userFileManager, 140 QSPipelineFlagsRepository featureFlags 141 ) { 142 mContext = context; 143 mUserContext = context; 144 mTunerService = tunerService; 145 mPluginManager = pluginManager; 146 mQSLogger = qsLogger; 147 mMainExecutor = mainExecutor; 148 mTileLifeCycleManagerFactory = tileLifecycleManagerFactory; 149 mUserFileManager = userFileManager; 150 mFeatureFlags = featureFlags; 151 152 mShadeControllerProvider = shadeControllerProvider; 153 154 if (featureFlags.getTilesEnabled()) { 155 mQsFactories.add(newQsTileFactoryProvider.get()); 156 } 157 mQsFactories.add(defaultFactory); 158 pluginManager.addPluginListener(this, QSFactory.class, true); 159 mUserTracker = userTracker; 160 mCurrentUser = userTracker.getUserId(); 161 mSecureSettings = secureSettings; 162 mCustomTileStatePersister = customTileStatePersister; 163 164 mainExecutor.execute(() -> { 165 // This is technically a hack to avoid circular dependency of 166 // QSTileHost -> XXXTile -> QSTileHost. Posting ensures creation 167 // finishes before creating any tiles. 168 tunerService.addTunable(this, TILES_SETTING); 169 // AutoTileManager can modify mTiles so make sure mTiles has already been initialized. 170 if (!mFeatureFlags.getPipelineEnabled()) { 171 mAutoTiles = autoTiles.get(); 172 } 173 }); 174 } 175 destroy()176 public void destroy() { 177 mTiles.values().forEach(tile -> tile.destroy()); 178 mAutoTiles.destroy(); 179 mTunerService.removeTunable(this); 180 mPluginManager.removePluginListener(this); 181 } 182 183 @Override onPluginConnected(QSFactory plugin, Context pluginContext)184 public void onPluginConnected(QSFactory plugin, Context pluginContext) { 185 // Give plugins priority over creation so they can override if they wish. 186 mQsFactories.add(0, plugin); 187 String value = mTunerService.getValue(TILES_SETTING); 188 // Force remove and recreate of all tiles. 189 onTuningChanged(TILES_SETTING, ""); 190 onTuningChanged(TILES_SETTING, value); 191 } 192 193 @Override onPluginDisconnected(QSFactory plugin)194 public void onPluginDisconnected(QSFactory plugin) { 195 mQsFactories.remove(plugin); 196 // Force remove and recreate of all tiles. 197 String value = mTunerService.getValue(TILES_SETTING); 198 onTuningChanged(TILES_SETTING, ""); 199 onTuningChanged(TILES_SETTING, value); 200 } 201 202 @Override addCallback(Callback callback)203 public void addCallback(Callback callback) { 204 mCallbacks.add(callback); 205 } 206 207 @Override removeCallback(Callback callback)208 public void removeCallback(Callback callback) { 209 mCallbacks.remove(callback); 210 } 211 212 @Override getTiles()213 public Collection<QSTile> getTiles() { 214 return mTiles.values(); 215 } 216 217 @Override collapsePanels()218 public void collapsePanels() { 219 mShadeControllerProvider.get().postAnimateCollapseShade(); 220 } 221 222 @Override forceCollapsePanels()223 public void forceCollapsePanels() { 224 mShadeControllerProvider.get().postAnimateForceCollapseShade(); 225 } 226 227 @Override openPanels()228 public void openPanels() { 229 mShadeControllerProvider.get().postAnimateExpandQs(); 230 } 231 232 @Override getContext()233 public Context getContext() { 234 return mContext; 235 } 236 237 @Override getUserContext()238 public Context getUserContext() { 239 return mUserContext; 240 } 241 242 @Override getUserId()243 public int getUserId() { 244 return mCurrentUser; 245 } 246 indexOf(String spec)247 public int indexOf(String spec) { 248 return mTileSpecs.indexOf(spec); 249 } 250 251 /** 252 * Whenever the Secure Setting keeping track of the current tiles changes (or upon start) this 253 * will be called with the new value of the setting. 254 * 255 * This method will do the following: 256 * <ol> 257 * <li>Destroy any existing tile that's not one of the current tiles (in the setting)</li> 258 * <li>Create new tiles for those that don't already exist. If this tiles end up being 259 * not available, they'll also be destroyed.</li> 260 * <li>Save the resolved list of tiles (current tiles that are available) into the setting. 261 * This means that after this call ends, the tiles in the Setting, {@link #mTileSpecs}, 262 * and visible tiles ({@link #mTiles}) must match. 263 * </li> 264 * </ol> 265 * 266 * Additionally, if the user has changed, it'll do the following: 267 * <ul> 268 * <li>Change the user for SystemUI tiles: {@link QSTile#userSwitch}.</li> 269 * <li>Destroy any {@link CustomTile} and recreate it for the new user.</li> 270 * </ul> 271 * 272 * This happens in main thread as {@link com.android.systemui.tuner.TunerServiceImpl} dispatches 273 * in main thread. 274 * 275 * @see QSTile#isAvailable 276 */ 277 @MainThread 278 @Override onTuningChanged(String key, String newValue)279 public void onTuningChanged(String key, String newValue) { 280 if (!TILES_SETTING.equals(key)) { 281 return; 282 } 283 int currentUser = mUserTracker.getUserId(); 284 if (currentUser != mCurrentUser) { 285 mUserContext = mUserTracker.getUserContext(); 286 if (mAutoTiles != null) { 287 mAutoTiles.changeUser(UserHandle.of(currentUser)); 288 } 289 } 290 // Do not process tiles if the flag is enabled. 291 if (mFeatureFlags.getPipelineEnabled()) { 292 return; 293 } 294 QSPipelineFlagsRepository.Utils.assertInLegacyMode(); 295 if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) { 296 newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode); 297 } 298 final List<String> tileSpecs = loadTileSpecs(mContext, newValue); 299 if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return; 300 Log.d(TAG, "Recreating tiles: " + tileSpecs); 301 mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach( 302 tile -> { 303 Log.d(TAG, "Destroying tile: " + tile.getKey()); 304 mQSLogger.logTileDestroyed(tile.getKey(), "Tile removed"); 305 tile.getValue().destroy(); 306 }); 307 final LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>(); 308 for (String tileSpec : tileSpecs) { 309 QSTile tile = mTiles.get(tileSpec); 310 if (tile != null && (!(tile instanceof CustomTile) 311 || ((CustomTile) tile).getUser() == currentUser)) { 312 if (tile.isAvailable()) { 313 Log.d(TAG, "Adding " + tile); 314 tile.removeCallbacks(); 315 if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) { 316 tile.userSwitch(currentUser); 317 } 318 newTiles.put(tileSpec, tile); 319 mQSLogger.logTileAdded(tileSpec); 320 } else { 321 tile.destroy(); 322 Log.d(TAG, "Destroying not available tile: " + tileSpec); 323 mQSLogger.logTileDestroyed(tileSpec, "Tile not available"); 324 } 325 } else { 326 // This means that the tile is a CustomTile AND the user is different, so let's 327 // destroy it 328 if (tile != null) { 329 tile.destroy(); 330 Log.d(TAG, "Destroying tile for wrong user: " + tileSpec); 331 mQSLogger.logTileDestroyed(tileSpec, "Tile for wrong user"); 332 } 333 Log.d(TAG, "Creating tile: " + tileSpec); 334 try { 335 tile = createTile(tileSpec); 336 if (tile != null) { 337 if (tile.isAvailable()) { 338 newTiles.put(tileSpec, tile); 339 mQSLogger.logTileAdded(tileSpec); 340 } else { 341 tile.destroy(); 342 Log.d(TAG, "Destroying not available tile: " + tileSpec); 343 mQSLogger.logTileDestroyed(tileSpec, "Tile not available"); 344 } 345 } else { 346 Log.d(TAG, "No factory for a spec: " + tileSpec); 347 } 348 } catch (Throwable t) { 349 Log.w(TAG, "Error creating tile for spec: " + tileSpec, t); 350 } 351 } 352 } 353 mCurrentUser = currentUser; 354 List<String> currentSpecs = new ArrayList<>(mTileSpecs); 355 mTileSpecs.clear(); 356 mTileSpecs.addAll(newTiles.keySet()); // Only add the valid (available) tiles. 357 mTiles.clear(); 358 mTiles.putAll(newTiles); 359 if (newTiles.isEmpty() && !tileSpecs.isEmpty()) { 360 // If we didn't manage to create any tiles, set it to empty (default) 361 Log.d(TAG, "No valid tiles on tuning changed. Setting to default."); 362 changeTilesByUser(currentSpecs, loadTileSpecs(mContext, "")); 363 } else { 364 String resolvedTiles = TextUtils.join(",", mTileSpecs); 365 if (!resolvedTiles.equals(newValue)) { 366 // If the resolved tiles (those we actually ended up with) are different than 367 // the ones that are in the setting, update the Setting. 368 saveTilesToSettings(mTileSpecs); 369 } 370 mTilesListDirty = false; 371 for (int i = 0; i < mCallbacks.size(); i++) { 372 mCallbacks.get(i).onTilesChanged(); 373 } 374 } 375 } 376 377 /** 378 * Only use with [CustomTile] if the tile doesn't exist anymore (and therefore doesn't need 379 * its lifecycle terminated). 380 */ 381 @Override removeTile(String spec)382 public void removeTile(String spec) { 383 if (spec.startsWith(CustomTile.PREFIX)) { 384 // If the tile is removed (due to it not actually existing), mark it as removed. That 385 // way it will be marked as newly added if it appears in the future. 386 setTileAdded(CustomTile.getComponentFromSpec(spec), mCurrentUser, false); 387 } 388 mMainExecutor.execute(() -> changeTileSpecs(tileSpecs-> tileSpecs.remove(spec))); 389 } 390 391 /** 392 * Remove many tiles at once. 393 * 394 * It will only save to settings once (as opposed to {@link QSTileHost#removeTileByUser} called 395 * multiple times). 396 */ 397 @Override removeTiles(Collection<String> specs)398 public void removeTiles(Collection<String> specs) { 399 mMainExecutor.execute(() -> changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs))); 400 } 401 402 /** 403 * Add a tile to the end 404 * 405 * @param spec string matching a pre-defined tilespec 406 */ addTile(String spec)407 public void addTile(String spec) { 408 addTile(spec, POSITION_AT_END); 409 } 410 411 @Override addTile(String spec, int requestPosition)412 public void addTile(String spec, int requestPosition) { 413 mMainExecutor.execute(() -> 414 changeTileSpecs(tileSpecs -> { 415 if (tileSpecs.contains(spec)) return false; 416 417 int size = tileSpecs.size(); 418 if (requestPosition == POSITION_AT_END || requestPosition >= size) { 419 tileSpecs.add(spec); 420 } else { 421 tileSpecs.add(requestPosition, spec); 422 } 423 return true; 424 }) 425 ); 426 } 427 428 // When calling this, you may want to modify mTilesListDirty accordingly. 429 @MainThread saveTilesToSettings(List<String> tileSpecs)430 private void saveTilesToSettings(List<String> tileSpecs) { 431 Log.d(TAG, "Saving tiles: " + tileSpecs + " for user: " + mCurrentUser); 432 mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs), 433 null /* tag */, false /* default */, mCurrentUser, 434 true /* overrideable by restore */); 435 } 436 437 @MainThread changeTileSpecs(Predicate<List<String>> changeFunction)438 private void changeTileSpecs(Predicate<List<String>> changeFunction) { 439 final List<String> tileSpecs; 440 if (!mTilesListDirty) { 441 tileSpecs = new ArrayList<>(mTileSpecs); 442 } else { 443 tileSpecs = loadTileSpecs(mContext, 444 mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser)); 445 } 446 if (changeFunction.test(tileSpecs)) { 447 mTilesListDirty = true; 448 saveTilesToSettings(tileSpecs); 449 } 450 } 451 452 @Override addTile(ComponentName tile)453 public void addTile(ComponentName tile) { 454 addTile(tile, /* end */ false); 455 } 456 457 @Override addTile(ComponentName tile, boolean end)458 public void addTile(ComponentName tile, boolean end) { 459 String spec = CustomTile.toSpec(tile); 460 addTile(spec, end ? POSITION_AT_END : 0); 461 } 462 463 /** 464 * This will call through {@link #changeTilesByUser}. It should only be used when a tile is 465 * removed by a <b>user action</b> like {@code adb}. 466 */ 467 @Override removeTileByUser(ComponentName tile)468 public void removeTileByUser(ComponentName tile) { 469 mMainExecutor.execute(() -> { 470 List<String> newSpecs = new ArrayList<>(mTileSpecs); 471 if (newSpecs.remove(CustomTile.toSpec(tile))) { 472 changeTilesByUser(mTileSpecs, newSpecs); 473 } 474 }); 475 } 476 477 /** 478 * Change the tiles triggered by the user editing. 479 * <p> 480 * This is not called on device start, or on user change. 481 * 482 * {@link android.service.quicksettings.TileService#onTileRemoved} will be called for tiles 483 * that are removed. 484 */ 485 @MainThread 486 @Override changeTilesByUser(List<String> previousTiles, List<String> newTiles)487 public void changeTilesByUser(List<String> previousTiles, List<String> newTiles) { 488 final List<String> copy = new ArrayList<>(previousTiles); 489 final int NP = copy.size(); 490 for (int i = 0; i < NP; i++) { 491 String tileSpec = copy.get(i); 492 if (!tileSpec.startsWith(CustomTile.PREFIX)) continue; 493 if (!newTiles.contains(tileSpec)) { 494 ComponentName component = CustomTile.getComponentFromSpec(tileSpec); 495 Intent intent = new Intent().setComponent(component); 496 TileLifecycleManager lifecycleManager = mTileLifeCycleManagerFactory.create( 497 intent, new UserHandle(mCurrentUser)); 498 lifecycleManager.onStopListening(); 499 lifecycleManager.onTileRemoved(); 500 mCustomTileStatePersister.removeState(new TileServiceKey(component, mCurrentUser)); 501 setTileAdded(component, mCurrentUser, false); 502 lifecycleManager.flushMessagesAndUnbind(); 503 } 504 } 505 Log.d(TAG, "saveCurrentTiles " + newTiles); 506 mTilesListDirty = true; 507 saveTilesToSettings(newTiles); 508 } 509 510 @Nullable 511 @Override createTile(String tileSpec)512 public QSTile createTile(String tileSpec) { 513 for (int i = 0; i < mQsFactories.size(); i++) { 514 QSTile t = mQsFactories.get(i).createTile(tileSpec); 515 if (t != null) { 516 return t; 517 } 518 } 519 return null; 520 } 521 522 /** 523 * Check if a particular {@link CustomTile} has been added for a user and has not been removed 524 * since. 525 * @param componentName the {@link ComponentName} of the 526 * {@link android.service.quicksettings.TileService} associated with the 527 * tile. 528 * @param userId the user to check 529 */ 530 @Override isTileAdded(ComponentName componentName, int userId)531 public boolean isTileAdded(ComponentName componentName, int userId) { 532 return mUserFileManager 533 .getSharedPreferences(TILES, 0, userId) 534 .getBoolean(componentName.flattenToString(), false); 535 } 536 537 /** 538 * Persists whether a particular {@link CustomTile} has been added and it's currently in the 539 * set of selected tiles ({@link #mTiles}. 540 * @param componentName the {@link ComponentName} of the 541 * {@link android.service.quicksettings.TileService} associated 542 * with the tile. 543 * @param userId the user for this tile 544 * @param added {@code true} if the tile is being added, {@code false} otherwise 545 */ 546 @Override setTileAdded(ComponentName componentName, int userId, boolean added)547 public void setTileAdded(ComponentName componentName, int userId, boolean added) { 548 mUserFileManager.getSharedPreferences(TILES, 0, userId) 549 .edit() 550 .putBoolean(componentName.flattenToString(), added) 551 .apply(); 552 } 553 554 @Override getSpecs()555 public List<String> getSpecs() { 556 return mTileSpecs; 557 } 558 loadTileSpecs(Context context, String tileList)559 protected static List<String> loadTileSpecs(Context context, String tileList) { 560 final Resources res = context.getResources(); 561 562 if (TextUtils.isEmpty(tileList)) { 563 tileList = res.getString(R.string.quick_settings_tiles); 564 Log.d(TAG, "Loaded tile specs from default config: " + tileList); 565 } else { 566 Log.d(TAG, "Loaded tile specs from setting: " + tileList); 567 } 568 final ArrayList<String> tiles = new ArrayList<String>(); 569 boolean addedDefault = false; 570 Set<String> addedSpecs = new ArraySet<>(); 571 for (String tile : tileList.split(",")) { 572 tile = tile.trim(); 573 if (tile.isEmpty()) continue; 574 if (tile.equals("default")) { 575 if (!addedDefault) { 576 List<String> defaultSpecs = QSHost.getDefaultSpecs(context.getResources()); 577 for (String spec : defaultSpecs) { 578 if (!addedSpecs.contains(spec)) { 579 tiles.add(spec); 580 addedSpecs.add(spec); 581 } 582 } 583 addedDefault = true; 584 } 585 } else { 586 if (!addedSpecs.contains(tile)) { 587 tiles.add(tile); 588 addedSpecs.add(tile); 589 } 590 } 591 } 592 593 if (!tiles.contains("internet")) { 594 if (tiles.contains("wifi")) { 595 // Replace the WiFi with Internet, and remove the Cell 596 tiles.set(tiles.indexOf("wifi"), "internet"); 597 tiles.remove("cell"); 598 } else if (tiles.contains("cell")) { 599 // Replace the Cell with Internet 600 tiles.set(tiles.indexOf("cell"), "internet"); 601 } 602 } else { 603 tiles.remove("wifi"); 604 tiles.remove("cell"); 605 } 606 return tiles; 607 } 608 609 @Override dump(PrintWriter pw, String[] args)610 public void dump(PrintWriter pw, String[] args) { 611 pw.println("QSTileHost:"); 612 pw.println("tile specs: " + mTileSpecs); 613 pw.println("current user: " + mCurrentUser); 614 pw.println("is dirty: " + mTilesListDirty); 615 pw.println("tiles:"); 616 mTiles.values().stream().filter(obj -> obj instanceof Dumpable) 617 .forEach(o -> ((Dumpable) o).dump(pw, args)); 618 } 619 620 @Override dumpProto(@otNull SystemUIProtoDump systemUIProtoDump, @NotNull String[] args)621 public void dumpProto(@NotNull SystemUIProtoDump systemUIProtoDump, @NotNull String[] args) { 622 List<QsTileState> data = mTiles.values().stream() 623 .map(QSTile::getState) 624 .map(TileStateToProtoKt::toProto) 625 .filter(Objects::nonNull) 626 .collect(Collectors.toList()); 627 628 systemUIProtoDump.tiles = data.toArray(new QsTileState[0]); 629 } 630 } 631