/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.phone; import android.app.ActivityManager; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Process; import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Secure; import android.service.quicksettings.Tile; import android.text.TextUtils; import android.util.Log; import android.view.View; import com.android.systemui.R; import com.android.systemui.qs.QSTile; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.external.TileLifecycleManager; import com.android.systemui.qs.external.TileServices; import com.android.systemui.qs.tiles.AirplaneModeTile; import com.android.systemui.qs.tiles.BatteryTile; import com.android.systemui.qs.tiles.BluetoothTile; import com.android.systemui.qs.tiles.CastTile; import com.android.systemui.qs.tiles.CellularTile; import com.android.systemui.qs.tiles.ColorInversionTile; import com.android.systemui.qs.tiles.DataSaverTile; import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.qs.tiles.FlashlightTile; import com.android.systemui.qs.tiles.HotspotTile; import com.android.systemui.qs.tiles.IntentTile; import com.android.systemui.qs.tiles.LocationTile; import com.android.systemui.qs.tiles.RotationLockTile; import com.android.systemui.qs.tiles.UserTile; import com.android.systemui.qs.tiles.WifiTile; import com.android.systemui.qs.tiles.WorkModeTile; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.NextAlarmController; import com.android.systemui.statusbar.policy.NightModeController; import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.LocationController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.tuner.NightModeTile; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** Platform implementation of the quick settings tile host **/ public class QSTileHost implements QSTile.Host, Tunable { private static final String TAG = "QSTileHost"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); public static final String TILES_SETTING = "sysui_qs_tiles"; private final Context mContext; private final PhoneStatusBar mStatusBar; private final LinkedHashMap> mTiles = new LinkedHashMap<>(); protected final ArrayList mTileSpecs = new ArrayList<>(); private final BluetoothController mBluetooth; private final LocationController mLocation; private final RotationLockController mRotation; private final NetworkController mNetwork; private final ZenModeController mZen; private final HotspotController mHotspot; private final CastController mCast; private final Looper mLooper; private final FlashlightController mFlashlight; private final UserSwitcherController mUserSwitcherController; private final UserInfoController mUserInfoController; private final KeyguardMonitor mKeyguard; private final SecurityController mSecurity; private final BatteryController mBattery; private final StatusBarIconController mIconController; private final TileServices mServices; private final List mCallbacks = new ArrayList<>(); private final NightModeController mNightModeController; private final AutoTileManager mAutoTiles; private final ManagedProfileController mProfileController; private final NextAlarmController mNextAlarmController; private View mHeader; private int mCurrentUser; public QSTileHost(Context context, PhoneStatusBar statusBar, BluetoothController bluetooth, LocationController location, RotationLockController rotation, NetworkController network, ZenModeController zen, HotspotController hotspot, CastController cast, FlashlightController flashlight, UserSwitcherController userSwitcher, UserInfoController userInfo, KeyguardMonitor keyguard, SecurityController security, BatteryController battery, StatusBarIconController iconController, NextAlarmController nextAlarmController) { mContext = context; mStatusBar = statusBar; mBluetooth = bluetooth; mLocation = location; mRotation = rotation; mNetwork = network; mZen = zen; mHotspot = hotspot; mCast = cast; mFlashlight = flashlight; mUserSwitcherController = userSwitcher; mUserInfoController = userInfo; mKeyguard = keyguard; mSecurity = security; mBattery = battery; mIconController = iconController; mNextAlarmController = nextAlarmController; mNightModeController = new NightModeController(mContext, true); mProfileController = new ManagedProfileController(this); final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName(), Process.THREAD_PRIORITY_BACKGROUND); ht.start(); mLooper = ht.getLooper(); mServices = new TileServices(this, mLooper); TunerService.get(mContext).addTunable(this, TILES_SETTING); // AutoTileManager can modify mTiles so make sure mTiles has already been initialized. mAutoTiles = new AutoTileManager(context, this); } public NextAlarmController getNextAlarmController() { return mNextAlarmController; } public void setHeaderView(View view) { mHeader = view; } public PhoneStatusBar getPhoneStatusBar() { return mStatusBar; } public void destroy() { mAutoTiles.destroy(); TunerService.get(mContext).removeTunable(this); } @Override public void addCallback(Callback callback) { mCallbacks.add(callback); } @Override public void removeCallback(Callback callback) { mCallbacks.remove(callback); } @Override public Collection> getTiles() { return mTiles.values(); } @Override public void startActivityDismissingKeyguard(final Intent intent) { mStatusBar.postStartActivityDismissingKeyguard(intent, 0); } @Override public void startActivityDismissingKeyguard(PendingIntent intent) { mStatusBar.postStartActivityDismissingKeyguard(intent); } @Override public void startRunnableDismissingKeyguard(Runnable runnable) { mStatusBar.postQSRunnableDismissingKeyguard(runnable); } @Override public void warn(String message, Throwable t) { // already logged } public void animateToggleQSExpansion() { // TODO: Better path to animated panel expansion. mHeader.callOnClick(); } @Override public void collapsePanels() { mStatusBar.postAnimateCollapsePanels(); } @Override public void openPanels() { mStatusBar.postAnimateOpenPanels(); } @Override public Looper getLooper() { return mLooper; } @Override public Context getContext() { return mContext; } @Override public BluetoothController getBluetoothController() { return mBluetooth; } @Override public LocationController getLocationController() { return mLocation; } @Override public RotationLockController getRotationLockController() { return mRotation; } @Override public NetworkController getNetworkController() { return mNetwork; } @Override public ZenModeController getZenModeController() { return mZen; } @Override public HotspotController getHotspotController() { return mHotspot; } @Override public CastController getCastController() { return mCast; } @Override public FlashlightController getFlashlightController() { return mFlashlight; } @Override public KeyguardMonitor getKeyguardMonitor() { return mKeyguard; } @Override public UserSwitcherController getUserSwitcherController() { return mUserSwitcherController; } @Override public UserInfoController getUserInfoController() { return mUserInfoController; } @Override public BatteryController getBatteryController() { return mBattery; } public SecurityController getSecurityController() { return mSecurity; } public TileServices getTileServices() { return mServices; } public StatusBarIconController getIconController() { return mIconController; } public NightModeController getNightModeController() { return mNightModeController; } public ManagedProfileController getManagedProfileController() { return mProfileController; } @Override public void onTuningChanged(String key, String newValue) { if (!TILES_SETTING.equals(key)) { return; } if (DEBUG) Log.d(TAG, "Recreating tiles"); final List tileSpecs = loadTileSpecs(mContext, newValue); int currentUser = ActivityManager.getCurrentUser(); if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return; for (Map.Entry> tile : mTiles.entrySet()) { if (!tileSpecs.contains(tile.getKey())) { if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey()); tile.getValue().destroy(); } } final LinkedHashMap> newTiles = new LinkedHashMap<>(); for (String tileSpec : tileSpecs) { QSTile tile = mTiles.get(tileSpec); if (tile != null && (!(tile instanceof CustomTile) || ((CustomTile) tile).getUser() == currentUser)) { if (DEBUG) Log.d(TAG, "Adding " + tile); tile.removeCallbacks(); newTiles.put(tileSpec, tile); } else { if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec); try { tile = createTile(tileSpec); if (tile != null && tile.isAvailable()) { tile.setTileSpec(tileSpec); newTiles.put(tileSpec, tile); } } catch (Throwable t) { Log.w(TAG, "Error creating tile for spec: " + tileSpec, t); } } } mCurrentUser = currentUser; mTileSpecs.clear(); mTileSpecs.addAll(tileSpecs); mTiles.clear(); mTiles.putAll(newTiles); for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).onTilesChanged(); } } @Override public void removeTile(String tileSpec) { ArrayList specs = new ArrayList<>(mTileSpecs); specs.remove(tileSpec); Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING, TextUtils.join(",", specs), ActivityManager.getCurrentUser()); } public void addTile(String spec) { final String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(), TILES_SETTING, ActivityManager.getCurrentUser()); final List tileSpecs = loadTileSpecs(mContext, setting); if (tileSpecs.contains(spec)) { return; } tileSpecs.add(spec); Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING, TextUtils.join(",", tileSpecs), ActivityManager.getCurrentUser()); } public void addTile(ComponentName tile) { List newSpecs = new ArrayList<>(mTileSpecs); newSpecs.add(0, CustomTile.toSpec(tile)); changeTiles(mTileSpecs, newSpecs); } public void removeTile(ComponentName tile) { List newSpecs = new ArrayList<>(mTileSpecs); newSpecs.remove(CustomTile.toSpec(tile)); changeTiles(mTileSpecs, newSpecs); } public void changeTiles(List previousTiles, List newTiles) { final int NP = previousTiles.size(); final int NA = newTiles.size(); for (int i = 0; i < NP; i++) { String tileSpec = previousTiles.get(i); if (!tileSpec.startsWith(CustomTile.PREFIX)) continue; if (!newTiles.contains(tileSpec)) { ComponentName component = CustomTile.getComponentFromSpec(tileSpec); Intent intent = new Intent().setComponent(component); TileLifecycleManager lifecycleManager = new TileLifecycleManager(new Handler(), mContext, mServices, new Tile(component), intent, new UserHandle(ActivityManager.getCurrentUser())); lifecycleManager.onStopListening(); lifecycleManager.onTileRemoved(); lifecycleManager.flushMessagesAndUnbind(); } } for (int i = 0; i < NA; i++) { String tileSpec = newTiles.get(i); if (!tileSpec.startsWith(CustomTile.PREFIX)) continue; if (!previousTiles.contains(tileSpec)) { ComponentName component = CustomTile.getComponentFromSpec(tileSpec); Intent intent = new Intent().setComponent(component); TileLifecycleManager lifecycleManager = new TileLifecycleManager(new Handler(), mContext, mServices, new Tile(component), intent, new UserHandle(ActivityManager.getCurrentUser())); lifecycleManager.onTileAdded(); lifecycleManager.flushMessagesAndUnbind(); } } if (DEBUG) Log.d(TAG, "saveCurrentTiles " + newTiles); Secure.putStringForUser(getContext().getContentResolver(), QSTileHost.TILES_SETTING, TextUtils.join(",", newTiles), ActivityManager.getCurrentUser()); } public QSTile createTile(String tileSpec) { if (tileSpec.equals("wifi")) return new WifiTile(this); else if (tileSpec.equals("bt")) return new BluetoothTile(this); else if (tileSpec.equals("cell")) return new CellularTile(this); else if (tileSpec.equals("dnd")) return new DndTile(this); else if (tileSpec.equals("inversion")) return new ColorInversionTile(this); else if (tileSpec.equals("airplane")) return new AirplaneModeTile(this); else if (tileSpec.equals("work")) return new WorkModeTile(this); else if (tileSpec.equals("rotation")) return new RotationLockTile(this); else if (tileSpec.equals("flashlight")) return new FlashlightTile(this); else if (tileSpec.equals("location")) return new LocationTile(this); else if (tileSpec.equals("cast")) return new CastTile(this); else if (tileSpec.equals("hotspot")) return new HotspotTile(this); else if (tileSpec.equals("user")) return new UserTile(this); else if (tileSpec.equals("battery")) return new BatteryTile(this); else if (tileSpec.equals("saver")) return new DataSaverTile(this); else if (tileSpec.equals(NightModeTile.NIGHT_MODE_SPEC)) return new NightModeTile(this); // Intent tiles. else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec); else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(this,tileSpec); else { Log.w(TAG, "Bad tile spec: " + tileSpec); return null; } } protected List loadTileSpecs(Context context, String tileList) { final Resources res = context.getResources(); final String defaultTileList = res.getString(R.string.quick_settings_tiles_default); if (tileList == null) { tileList = res.getString(R.string.quick_settings_tiles); if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList); } else { if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList); } final ArrayList tiles = new ArrayList(); boolean addedDefault = false; for (String tile : tileList.split(",")) { tile = tile.trim(); if (tile.isEmpty()) continue; if (tile.equals("default")) { if (!addedDefault) { tiles.addAll(Arrays.asList(defaultTileList.split(","))); addedDefault = true; } } else { tiles.add(tile); } } return tiles; } }