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