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.os.UserManager; 31 import android.provider.Settings; 32 import android.provider.Settings.Secure; 33 import android.service.quicksettings.Tile; 34 import android.text.TextUtils; 35 import android.util.Log; 36 import android.view.View; 37 38 import com.android.systemui.R; 39 import com.android.systemui.qs.QSTile; 40 import com.android.systemui.qs.external.CustomTile; 41 import com.android.systemui.qs.external.TileLifecycleManager; 42 import com.android.systemui.qs.external.TileServices; 43 import com.android.systemui.qs.tiles.AirplaneModeTile; 44 import com.android.systemui.qs.tiles.BatteryTile; 45 import com.android.systemui.qs.tiles.BluetoothTile; 46 import com.android.systemui.qs.tiles.CastTile; 47 import com.android.systemui.qs.tiles.CellularTile; 48 import com.android.systemui.qs.tiles.ColorInversionTile; 49 import com.android.systemui.qs.tiles.DataSaverTile; 50 import com.android.systemui.qs.tiles.DndTile; 51 import com.android.systemui.qs.tiles.FlashlightTile; 52 import com.android.systemui.qs.tiles.HotspotTile; 53 import com.android.systemui.qs.tiles.IntentTile; 54 import com.android.systemui.qs.tiles.LocationTile; 55 import com.android.systemui.qs.tiles.NightDisplayTile; 56 import com.android.systemui.qs.tiles.RotationLockTile; 57 import com.android.systemui.qs.tiles.UserTile; 58 import com.android.systemui.qs.tiles.WifiTile; 59 import com.android.systemui.qs.tiles.WorkModeTile; 60 import com.android.systemui.statusbar.policy.BatteryController; 61 import com.android.systemui.statusbar.policy.BluetoothController; 62 import com.android.systemui.statusbar.policy.CastController; 63 import com.android.systemui.statusbar.policy.NextAlarmController; 64 import com.android.systemui.statusbar.policy.FlashlightController; 65 import com.android.systemui.statusbar.policy.HotspotController; 66 import com.android.systemui.statusbar.policy.KeyguardMonitor; 67 import com.android.systemui.statusbar.policy.LocationController; 68 import com.android.systemui.statusbar.policy.NetworkController; 69 import com.android.systemui.statusbar.policy.RotationLockController; 70 import com.android.systemui.statusbar.policy.SecurityController; 71 import com.android.systemui.statusbar.policy.UserInfoController; 72 import com.android.systemui.statusbar.policy.UserSwitcherController; 73 import com.android.systemui.statusbar.policy.ZenModeController; 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 = Secure.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 AutoTileManager mAutoTiles; 114 private final ManagedProfileController mProfileController; 115 private final NextAlarmController mNextAlarmController; 116 private View mHeader; 117 private int mCurrentUser; 118 119 public QSTileHost(Context context, PhoneStatusBar statusBar, 120 BluetoothController bluetooth, LocationController location, 121 RotationLockController rotation, NetworkController network, 122 ZenModeController zen, HotspotController hotspot, 123 CastController cast, FlashlightController flashlight, 124 UserSwitcherController userSwitcher, UserInfoController userInfo, 125 KeyguardMonitor keyguard, SecurityController security, 126 BatteryController battery, StatusBarIconController iconController, 127 NextAlarmController nextAlarmController) { 128 mContext = context; 129 mStatusBar = statusBar; 130 mBluetooth = bluetooth; 131 mLocation = location; 132 mRotation = rotation; 133 mNetwork = network; 134 mZen = zen; 135 mHotspot = hotspot; 136 mCast = cast; 137 mFlashlight = flashlight; 138 mUserSwitcherController = userSwitcher; 139 mUserInfoController = userInfo; 140 mKeyguard = keyguard; 141 mSecurity = security; 142 mBattery = battery; 143 mIconController = iconController; 144 mNextAlarmController = nextAlarmController; 145 mProfileController = new ManagedProfileController(this); 146 147 final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName(), 148 Process.THREAD_PRIORITY_BACKGROUND); 149 ht.start(); 150 mLooper = ht.getLooper(); 151 152 mServices = new TileServices(this, mLooper); 153 154 TunerService.get(mContext).addTunable(this, TILES_SETTING); 155 // AutoTileManager can modify mTiles so make sure mTiles has already been initialized. 156 mAutoTiles = new AutoTileManager(context, this); 157 } 158 159 public NextAlarmController getNextAlarmController() { 160 return mNextAlarmController; 161 } 162 163 public void setHeaderView(View view) { 164 mHeader = view; 165 } 166 167 public PhoneStatusBar getPhoneStatusBar() { 168 return mStatusBar; 169 } 170 171 public void destroy() { 172 mAutoTiles.destroy(); 173 TunerService.get(mContext).removeTunable(this); 174 } 175 176 @Override 177 public void addCallback(Callback callback) { 178 mCallbacks.add(callback); 179 } 180 181 @Override 182 public void removeCallback(Callback callback) { 183 mCallbacks.remove(callback); 184 } 185 186 @Override 187 public Collection<QSTile<?>> getTiles() { 188 return mTiles.values(); 189 } 190 191 @Override 192 public void startActivityDismissingKeyguard(final Intent intent) { 193 mStatusBar.postStartActivityDismissingKeyguard(intent, 0); 194 } 195 196 @Override 197 public void startActivityDismissingKeyguard(PendingIntent intent) { 198 mStatusBar.postStartActivityDismissingKeyguard(intent); 199 } 200 201 @Override 202 public void startRunnableDismissingKeyguard(Runnable runnable) { 203 mStatusBar.postQSRunnableDismissingKeyguard(runnable); 204 } 205 206 @Override 207 public void warn(String message, Throwable t) { 208 // already logged 209 } 210 211 public void animateToggleQSExpansion() { 212 // TODO: Better path to animated panel expansion. 213 mHeader.callOnClick(); 214 } 215 216 @Override 217 public void collapsePanels() { 218 mStatusBar.postAnimateCollapsePanels(); 219 } 220 221 @Override 222 public void openPanels() { 223 mStatusBar.postAnimateOpenPanels(); 224 } 225 226 @Override 227 public Looper getLooper() { 228 return mLooper; 229 } 230 231 @Override 232 public Context getContext() { 233 return mContext; 234 } 235 236 @Override 237 public BluetoothController getBluetoothController() { 238 return mBluetooth; 239 } 240 241 @Override 242 public LocationController getLocationController() { 243 return mLocation; 244 } 245 246 @Override 247 public RotationLockController getRotationLockController() { 248 return mRotation; 249 } 250 251 @Override 252 public NetworkController getNetworkController() { 253 return mNetwork; 254 } 255 256 @Override 257 public ZenModeController getZenModeController() { 258 return mZen; 259 } 260 261 @Override 262 public HotspotController getHotspotController() { 263 return mHotspot; 264 } 265 266 @Override 267 public CastController getCastController() { 268 return mCast; 269 } 270 271 @Override 272 public FlashlightController getFlashlightController() { 273 return mFlashlight; 274 } 275 276 @Override 277 public KeyguardMonitor getKeyguardMonitor() { 278 return mKeyguard; 279 } 280 281 @Override 282 public UserSwitcherController getUserSwitcherController() { 283 return mUserSwitcherController; 284 } 285 286 @Override 287 public UserInfoController getUserInfoController() { 288 return mUserInfoController; 289 } 290 291 @Override 292 public BatteryController getBatteryController() { 293 return mBattery; 294 } 295 296 public SecurityController getSecurityController() { 297 return mSecurity; 298 } 299 300 public TileServices getTileServices() { 301 return mServices; 302 } 303 304 public StatusBarIconController getIconController() { 305 return mIconController; 306 } 307 308 public ManagedProfileController getManagedProfileController() { 309 return mProfileController; 310 } 311 312 @Override 313 public void onTuningChanged(String key, String newValue) { 314 if (!TILES_SETTING.equals(key)) { 315 return; 316 } 317 if (DEBUG) Log.d(TAG, "Recreating tiles"); 318 if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) { 319 newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode); 320 } 321 final List<String> tileSpecs = loadTileSpecs(mContext, newValue); 322 int currentUser = ActivityManager.getCurrentUser(); 323 if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return; 324 for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) { 325 if (!tileSpecs.contains(tile.getKey())) { 326 if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey()); 327 tile.getValue().destroy(); 328 } 329 } 330 final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>(); 331 for (String tileSpec : tileSpecs) { 332 QSTile<?> tile = mTiles.get(tileSpec); 333 if (tile != null && (!(tile instanceof CustomTile) 334 || ((CustomTile) tile).getUser() == currentUser)) { 335 if (DEBUG) Log.d(TAG, "Adding " + tile); 336 tile.removeCallbacks(); 337 if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) { 338 tile.userSwitch(currentUser); 339 } 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(), intent, 407 new UserHandle(ActivityManager.getCurrentUser())); 408 lifecycleManager.onStopListening(); 409 lifecycleManager.onTileRemoved(); 410 TileLifecycleManager.setTileAdded(mContext, component, false); 411 lifecycleManager.flushMessagesAndUnbind(); 412 } 413 } 414 if (DEBUG) Log.d(TAG, "saveCurrentTiles " + newTiles); 415 Secure.putStringForUser(getContext().getContentResolver(), QSTileHost.TILES_SETTING, 416 TextUtils.join(",", newTiles), ActivityManager.getCurrentUser()); 417 } 418 419 public QSTile<?> createTile(String tileSpec) { 420 if (tileSpec.equals("wifi")) return new WifiTile(this); 421 else if (tileSpec.equals("bt")) return new BluetoothTile(this); 422 else if (tileSpec.equals("cell")) return new CellularTile(this); 423 else if (tileSpec.equals("dnd")) return new DndTile(this); 424 else if (tileSpec.equals("inversion")) return new ColorInversionTile(this); 425 else if (tileSpec.equals("airplane")) return new AirplaneModeTile(this); 426 else if (tileSpec.equals("work")) return new WorkModeTile(this); 427 else if (tileSpec.equals("rotation")) return new RotationLockTile(this); 428 else if (tileSpec.equals("flashlight")) return new FlashlightTile(this); 429 else if (tileSpec.equals("location")) return new LocationTile(this); 430 else if (tileSpec.equals("cast")) return new CastTile(this); 431 else if (tileSpec.equals("hotspot")) return new HotspotTile(this); 432 else if (tileSpec.equals("user")) return new UserTile(this); 433 else if (tileSpec.equals("battery")) return new BatteryTile(this); 434 else if (tileSpec.equals("saver")) return new DataSaverTile(this); 435 else if (tileSpec.equals("night")) return new NightDisplayTile(this); 436 // Intent tiles. 437 else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec); 438 else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(this,tileSpec); 439 else { 440 Log.w(TAG, "Bad tile spec: " + tileSpec); 441 return null; 442 } 443 } 444 445 protected List<String> loadTileSpecs(Context context, String tileList) { 446 final Resources res = context.getResources(); 447 final String defaultTileList = res.getString(R.string.quick_settings_tiles_default); 448 if (tileList == null) { 449 tileList = res.getString(R.string.quick_settings_tiles); 450 if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList); 451 } else { 452 if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList); 453 } 454 final ArrayList<String> tiles = new ArrayList<String>(); 455 boolean addedDefault = false; 456 for (String tile : tileList.split(",")) { 457 tile = tile.trim(); 458 if (tile.isEmpty()) continue; 459 if (tile.equals("default")) { 460 if (!addedDefault) { 461 tiles.addAll(Arrays.asList(defaultTileList.split(","))); 462 addedDefault = true; 463 } 464 } else { 465 tiles.add(tile); 466 } 467 } 468 return tiles; 469 } 470 } 471