1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.statusbar.phone; 18 19 import android.app.ActivityManager; 20 import android.app.PendingIntent; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.res.Resources; 25 import android.os.Handler; 26 import android.os.HandlerThread; 27 import android.os.Looper; 28 import android.os.Process; 29 import android.os.UserHandle; 30 import android.provider.Settings; 31 import android.provider.Settings.Secure; 32 import android.service.quicksettings.Tile; 33 import android.text.TextUtils; 34 import android.util.Log; 35 import android.view.View; 36 37 import com.android.systemui.R; 38 import com.android.systemui.qs.QSTile; 39 import com.android.systemui.qs.external.CustomTile; 40 import com.android.systemui.qs.external.TileLifecycleManager; 41 import com.android.systemui.qs.external.TileServices; 42 import com.android.systemui.qs.tiles.AirplaneModeTile; 43 import com.android.systemui.qs.tiles.BatteryTile; 44 import com.android.systemui.qs.tiles.BluetoothTile; 45 import com.android.systemui.qs.tiles.CastTile; 46 import com.android.systemui.qs.tiles.CellularTile; 47 import com.android.systemui.qs.tiles.ColorInversionTile; 48 import com.android.systemui.qs.tiles.DataSaverTile; 49 import com.android.systemui.qs.tiles.DndTile; 50 import com.android.systemui.qs.tiles.FlashlightTile; 51 import com.android.systemui.qs.tiles.HotspotTile; 52 import com.android.systemui.qs.tiles.IntentTile; 53 import com.android.systemui.qs.tiles.LocationTile; 54 import com.android.systemui.qs.tiles.RotationLockTile; 55 import com.android.systemui.qs.tiles.UserTile; 56 import com.android.systemui.qs.tiles.WifiTile; 57 import com.android.systemui.qs.tiles.WorkModeTile; 58 import com.android.systemui.statusbar.policy.BatteryController; 59 import com.android.systemui.statusbar.policy.BluetoothController; 60 import com.android.systemui.statusbar.policy.CastController; 61 import com.android.systemui.statusbar.policy.NextAlarmController; 62 import com.android.systemui.statusbar.policy.NightModeController; 63 import com.android.systemui.statusbar.policy.FlashlightController; 64 import com.android.systemui.statusbar.policy.HotspotController; 65 import com.android.systemui.statusbar.policy.KeyguardMonitor; 66 import com.android.systemui.statusbar.policy.LocationController; 67 import com.android.systemui.statusbar.policy.NetworkController; 68 import com.android.systemui.statusbar.policy.RotationLockController; 69 import com.android.systemui.statusbar.policy.SecurityController; 70 import com.android.systemui.statusbar.policy.UserInfoController; 71 import com.android.systemui.statusbar.policy.UserSwitcherController; 72 import com.android.systemui.statusbar.policy.ZenModeController; 73 import com.android.systemui.tuner.NightModeTile; 74 import com.android.systemui.tuner.TunerService; 75 import com.android.systemui.tuner.TunerService.Tunable; 76 77 import java.util.ArrayList; 78 import java.util.Arrays; 79 import java.util.Collection; 80 import java.util.LinkedHashMap; 81 import java.util.List; 82 import java.util.Map; 83 84 /** Platform implementation of the quick settings tile host **/ 85 public class QSTileHost implements QSTile.Host, Tunable { 86 private static final String TAG = "QSTileHost"; 87 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 88 89 public static final String TILES_SETTING = "sysui_qs_tiles"; 90 91 private final Context mContext; 92 private final PhoneStatusBar mStatusBar; 93 private final LinkedHashMap<String, QSTile<?>> mTiles = new LinkedHashMap<>(); 94 protected final ArrayList<String> mTileSpecs = new ArrayList<>(); 95 private final BluetoothController mBluetooth; 96 private final LocationController mLocation; 97 private final RotationLockController mRotation; 98 private final NetworkController mNetwork; 99 private final ZenModeController mZen; 100 private final HotspotController mHotspot; 101 private final CastController mCast; 102 private final Looper mLooper; 103 private final FlashlightController mFlashlight; 104 private final UserSwitcherController mUserSwitcherController; 105 private final UserInfoController mUserInfoController; 106 private final KeyguardMonitor mKeyguard; 107 private final SecurityController mSecurity; 108 private final BatteryController mBattery; 109 private final StatusBarIconController mIconController; 110 private final TileServices mServices; 111 112 private final List<Callback> mCallbacks = new ArrayList<>(); 113 private final NightModeController mNightModeController; 114 private final AutoTileManager mAutoTiles; 115 private final ManagedProfileController mProfileController; 116 private final NextAlarmController mNextAlarmController; 117 private View mHeader; 118 private int mCurrentUser; 119 120 public QSTileHost(Context context, PhoneStatusBar statusBar, 121 BluetoothController bluetooth, LocationController location, 122 RotationLockController rotation, NetworkController network, 123 ZenModeController zen, HotspotController hotspot, 124 CastController cast, FlashlightController flashlight, 125 UserSwitcherController userSwitcher, UserInfoController userInfo, 126 KeyguardMonitor keyguard, SecurityController security, 127 BatteryController battery, StatusBarIconController iconController, 128 NextAlarmController nextAlarmController) { 129 mContext = context; 130 mStatusBar = statusBar; 131 mBluetooth = bluetooth; 132 mLocation = location; 133 mRotation = rotation; 134 mNetwork = network; 135 mZen = zen; 136 mHotspot = hotspot; 137 mCast = cast; 138 mFlashlight = flashlight; 139 mUserSwitcherController = userSwitcher; 140 mUserInfoController = userInfo; 141 mKeyguard = keyguard; 142 mSecurity = security; 143 mBattery = battery; 144 mIconController = iconController; 145 mNextAlarmController = nextAlarmController; 146 mNightModeController = new NightModeController(mContext, true); 147 mProfileController = new ManagedProfileController(this); 148 149 final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName(), 150 Process.THREAD_PRIORITY_BACKGROUND); 151 ht.start(); 152 mLooper = ht.getLooper(); 153 154 mServices = new TileServices(this, mLooper); 155 156 TunerService.get(mContext).addTunable(this, TILES_SETTING); 157 // AutoTileManager can modify mTiles so make sure mTiles has already been initialized. 158 mAutoTiles = new AutoTileManager(context, this); 159 } 160 161 public NextAlarmController getNextAlarmController() { 162 return mNextAlarmController; 163 } 164 165 public void setHeaderView(View view) { 166 mHeader = view; 167 } 168 169 public PhoneStatusBar getPhoneStatusBar() { 170 return mStatusBar; 171 } 172 173 public void destroy() { 174 mAutoTiles.destroy(); 175 TunerService.get(mContext).removeTunable(this); 176 } 177 178 @Override 179 public void addCallback(Callback callback) { 180 mCallbacks.add(callback); 181 } 182 183 @Override 184 public void removeCallback(Callback callback) { 185 mCallbacks.remove(callback); 186 } 187 188 @Override 189 public Collection<QSTile<?>> getTiles() { 190 return mTiles.values(); 191 } 192 193 @Override 194 public void startActivityDismissingKeyguard(final Intent intent) { 195 mStatusBar.postStartActivityDismissingKeyguard(intent, 0); 196 } 197 198 @Override 199 public void startActivityDismissingKeyguard(PendingIntent intent) { 200 mStatusBar.postStartActivityDismissingKeyguard(intent); 201 } 202 203 @Override 204 public void startRunnableDismissingKeyguard(Runnable runnable) { 205 mStatusBar.postQSRunnableDismissingKeyguard(runnable); 206 } 207 208 @Override 209 public void warn(String message, Throwable t) { 210 // already logged 211 } 212 213 public void animateToggleQSExpansion() { 214 // TODO: Better path to animated panel expansion. 215 mHeader.callOnClick(); 216 } 217 218 @Override 219 public void collapsePanels() { 220 mStatusBar.postAnimateCollapsePanels(); 221 } 222 223 @Override 224 public void openPanels() { 225 mStatusBar.postAnimateOpenPanels(); 226 } 227 228 @Override 229 public Looper getLooper() { 230 return mLooper; 231 } 232 233 @Override 234 public Context getContext() { 235 return mContext; 236 } 237 238 @Override 239 public BluetoothController getBluetoothController() { 240 return mBluetooth; 241 } 242 243 @Override 244 public LocationController getLocationController() { 245 return mLocation; 246 } 247 248 @Override 249 public RotationLockController getRotationLockController() { 250 return mRotation; 251 } 252 253 @Override 254 public NetworkController getNetworkController() { 255 return mNetwork; 256 } 257 258 @Override 259 public ZenModeController getZenModeController() { 260 return mZen; 261 } 262 263 @Override 264 public HotspotController getHotspotController() { 265 return mHotspot; 266 } 267 268 @Override 269 public CastController getCastController() { 270 return mCast; 271 } 272 273 @Override 274 public FlashlightController getFlashlightController() { 275 return mFlashlight; 276 } 277 278 @Override 279 public KeyguardMonitor getKeyguardMonitor() { 280 return mKeyguard; 281 } 282 283 @Override 284 public UserSwitcherController getUserSwitcherController() { 285 return mUserSwitcherController; 286 } 287 288 @Override 289 public UserInfoController getUserInfoController() { 290 return mUserInfoController; 291 } 292 293 @Override 294 public BatteryController getBatteryController() { 295 return mBattery; 296 } 297 298 public SecurityController getSecurityController() { 299 return mSecurity; 300 } 301 302 public TileServices getTileServices() { 303 return mServices; 304 } 305 306 public StatusBarIconController getIconController() { 307 return mIconController; 308 } 309 310 public NightModeController getNightModeController() { 311 return mNightModeController; 312 } 313 314 public ManagedProfileController getManagedProfileController() { 315 return mProfileController; 316 } 317 318 @Override 319 public void onTuningChanged(String key, String newValue) { 320 if (!TILES_SETTING.equals(key)) { 321 return; 322 } 323 if (DEBUG) Log.d(TAG, "Recreating tiles"); 324 final List<String> tileSpecs = loadTileSpecs(mContext, newValue); 325 int currentUser = ActivityManager.getCurrentUser(); 326 if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return; 327 for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) { 328 if (!tileSpecs.contains(tile.getKey())) { 329 if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey()); 330 tile.getValue().destroy(); 331 } 332 } 333 final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>(); 334 for (String tileSpec : tileSpecs) { 335 QSTile<?> tile = mTiles.get(tileSpec); 336 if (tile != null && (!(tile instanceof CustomTile) 337 || ((CustomTile) tile).getUser() == currentUser)) { 338 if (DEBUG) Log.d(TAG, "Adding " + tile); 339 tile.removeCallbacks(); 340 newTiles.put(tileSpec, tile); 341 } else { 342 if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec); 343 try { 344 tile = createTile(tileSpec); 345 if (tile != null && tile.isAvailable()) { 346 tile.setTileSpec(tileSpec); 347 newTiles.put(tileSpec, tile); 348 } 349 } catch (Throwable t) { 350 Log.w(TAG, "Error creating tile for spec: " + tileSpec, t); 351 } 352 } 353 } 354 mCurrentUser = currentUser; 355 mTileSpecs.clear(); 356 mTileSpecs.addAll(tileSpecs); 357 mTiles.clear(); 358 mTiles.putAll(newTiles); 359 for (int i = 0; i < mCallbacks.size(); i++) { 360 mCallbacks.get(i).onTilesChanged(); 361 } 362 } 363 364 @Override 365 public void removeTile(String tileSpec) { 366 ArrayList<String> specs = new ArrayList<>(mTileSpecs); 367 specs.remove(tileSpec); 368 Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING, 369 TextUtils.join(",", specs), ActivityManager.getCurrentUser()); 370 } 371 372 public void addTile(String spec) { 373 final String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(), 374 TILES_SETTING, ActivityManager.getCurrentUser()); 375 final List<String> tileSpecs = loadTileSpecs(mContext, setting); 376 if (tileSpecs.contains(spec)) { 377 return; 378 } 379 tileSpecs.add(spec); 380 Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING, 381 TextUtils.join(",", tileSpecs), ActivityManager.getCurrentUser()); 382 } 383 384 public void addTile(ComponentName tile) { 385 List<String> newSpecs = new ArrayList<>(mTileSpecs); 386 newSpecs.add(0, CustomTile.toSpec(tile)); 387 changeTiles(mTileSpecs, newSpecs); 388 } 389 390 public void removeTile(ComponentName tile) { 391 List<String> newSpecs = new ArrayList<>(mTileSpecs); 392 newSpecs.remove(CustomTile.toSpec(tile)); 393 changeTiles(mTileSpecs, newSpecs); 394 } 395 396 public void changeTiles(List<String> previousTiles, List<String> newTiles) { 397 final int NP = previousTiles.size(); 398 final int NA = newTiles.size(); 399 for (int i = 0; i < NP; i++) { 400 String tileSpec = previousTiles.get(i); 401 if (!tileSpec.startsWith(CustomTile.PREFIX)) continue; 402 if (!newTiles.contains(tileSpec)) { 403 ComponentName component = CustomTile.getComponentFromSpec(tileSpec); 404 Intent intent = new Intent().setComponent(component); 405 TileLifecycleManager lifecycleManager = new TileLifecycleManager(new Handler(), 406 mContext, mServices, new Tile(component), intent, 407 new UserHandle(ActivityManager.getCurrentUser())); 408 lifecycleManager.onStopListening(); 409 lifecycleManager.onTileRemoved(); 410 lifecycleManager.flushMessagesAndUnbind(); 411 } 412 } 413 for (int i = 0; i < NA; i++) { 414 String tileSpec = newTiles.get(i); 415 if (!tileSpec.startsWith(CustomTile.PREFIX)) continue; 416 if (!previousTiles.contains(tileSpec)) { 417 ComponentName component = CustomTile.getComponentFromSpec(tileSpec); 418 Intent intent = new Intent().setComponent(component); 419 TileLifecycleManager lifecycleManager = new TileLifecycleManager(new Handler(), 420 mContext, mServices, new Tile(component), intent, 421 new UserHandle(ActivityManager.getCurrentUser())); 422 lifecycleManager.onTileAdded(); 423 lifecycleManager.flushMessagesAndUnbind(); 424 } 425 } 426 if (DEBUG) Log.d(TAG, "saveCurrentTiles " + newTiles); 427 Secure.putStringForUser(getContext().getContentResolver(), QSTileHost.TILES_SETTING, 428 TextUtils.join(",", newTiles), ActivityManager.getCurrentUser()); 429 } 430 431 public QSTile<?> createTile(String tileSpec) { 432 if (tileSpec.equals("wifi")) return new WifiTile(this); 433 else if (tileSpec.equals("bt")) return new BluetoothTile(this); 434 else if (tileSpec.equals("cell")) return new CellularTile(this); 435 else if (tileSpec.equals("dnd")) return new DndTile(this); 436 else if (tileSpec.equals("inversion")) return new ColorInversionTile(this); 437 else if (tileSpec.equals("airplane")) return new AirplaneModeTile(this); 438 else if (tileSpec.equals("work")) return new WorkModeTile(this); 439 else if (tileSpec.equals("rotation")) return new RotationLockTile(this); 440 else if (tileSpec.equals("flashlight")) return new FlashlightTile(this); 441 else if (tileSpec.equals("location")) return new LocationTile(this); 442 else if (tileSpec.equals("cast")) return new CastTile(this); 443 else if (tileSpec.equals("hotspot")) return new HotspotTile(this); 444 else if (tileSpec.equals("user")) return new UserTile(this); 445 else if (tileSpec.equals("battery")) return new BatteryTile(this); 446 else if (tileSpec.equals("saver")) return new DataSaverTile(this); 447 else if (tileSpec.equals(NightModeTile.NIGHT_MODE_SPEC)) 448 return new NightModeTile(this); 449 // Intent tiles. 450 else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec); 451 else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(this,tileSpec); 452 else { 453 Log.w(TAG, "Bad tile spec: " + tileSpec); 454 return null; 455 } 456 } 457 458 protected List<String> loadTileSpecs(Context context, String tileList) { 459 final Resources res = context.getResources(); 460 final String defaultTileList = res.getString(R.string.quick_settings_tiles_default); 461 if (tileList == null) { 462 tileList = res.getString(R.string.quick_settings_tiles); 463 if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList); 464 } else { 465 if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList); 466 } 467 final ArrayList<String> tiles = new ArrayList<String>(); 468 boolean addedDefault = false; 469 for (String tile : tileList.split(",")) { 470 tile = tile.trim(); 471 if (tile.isEmpty()) continue; 472 if (tile.equals("default")) { 473 if (!addedDefault) { 474 tiles.addAll(Arrays.asList(defaultTileList.split(","))); 475 addedDefault = true; 476 } 477 } else { 478 tiles.add(tile); 479 } 480 } 481 return tiles; 482 } 483 } 484