blob: dd567e8c802bf0be32227fbcdba0269d7d224616 [file] [log] [blame]
/*
* 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.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.service.quicksettings.Tile;
import android.text.TextUtils;
import android.util.Log;
import com.android.systemui.Dependency;
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.NfcTile;
import com.android.systemui.qs.tiles.NightDisplayTile;
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.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;
/** 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 = Secure.QS_TILES;
private final Context mContext;
private final StatusBar mStatusBar;
private final LinkedHashMap<String, QSTile<?>> mTiles = new LinkedHashMap<>();
protected final ArrayList<String> mTileSpecs = new ArrayList<>();
private final TileServices mServices;
private final List<Callback> mCallbacks = new ArrayList<>();
private final AutoTileManager mAutoTiles;
private final StatusBarIconController mIconController;
private int mCurrentUser;
public QSTileHost(Context context, StatusBar statusBar,
StatusBarIconController iconController) {
mIconController = iconController;
mContext = context;
mStatusBar = statusBar;
mServices = new TileServices(this, Dependency.get(Dependency.BG_LOOPER));
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 StatusBarIconController getIconController() {
return mIconController;
}
public void destroy() {
mTiles.values().forEach(tile -> tile.destroy());
mAutoTiles.destroy();
TunerService.get(mContext).removeTunable(this);
mServices.destroy();
}
@Override
public void addCallback(Callback callback) {
mCallbacks.add(callback);
}
@Override
public void removeCallback(Callback callback) {
mCallbacks.remove(callback);
}
@Override
public Collection<QSTile<?>> getTiles() {
return mTiles.values();
}
@Override
public void warn(String message, Throwable t) {
// already logged
}
@Override
public void collapsePanels() {
mStatusBar.postAnimateCollapsePanels();
}
@Override
public void openPanels() {
mStatusBar.postAnimateOpenPanels();
}
@Override
public Context getContext() {
return mContext;
}
public TileServices getTileServices() {
return mServices;
}
@Override
public void onTuningChanged(String key, String newValue) {
if (!TILES_SETTING.equals(key)) {
return;
}
if (DEBUG) Log.d(TAG, "Recreating tiles");
if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
}
final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
int currentUser = ActivityManager.getCurrentUser();
if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
tile -> {
if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
tile.getValue().destroy();
});
final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>();
for (String tileSpec : tileSpecs) {
QSTile<?> tile = mTiles.get(tileSpec);
if (tile != null && (!(tile instanceof CustomTile)
|| ((CustomTile) tile).getUser() == currentUser)) {
if (tile.isAvailable()) {
if (DEBUG) Log.d(TAG, "Adding " + tile);
tile.removeCallbacks();
if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) {
tile.userSwitch(currentUser);
}
newTiles.put(tileSpec, tile);
} else {
tile.destroy();
}
} else {
if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
try {
tile = createTile(tileSpec);
if (tile != null) {
if (tile.isAvailable()) {
tile.setTileSpec(tileSpec);
newTiles.put(tileSpec, tile);
} else {
tile.destroy();
}
}
} 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<String> 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<String> 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<String> newSpecs = new ArrayList<>(mTileSpecs);
newSpecs.add(0, CustomTile.toSpec(tile));
changeTiles(mTileSpecs, newSpecs);
}
public void removeTile(ComponentName tile) {
List<String> newSpecs = new ArrayList<>(mTileSpecs);
newSpecs.remove(CustomTile.toSpec(tile));
changeTiles(mTileSpecs, newSpecs);
}
public void changeTiles(List<String> previousTiles, List<String> 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(), intent,
new UserHandle(ActivityManager.getCurrentUser()));
lifecycleManager.onStopListening();
lifecycleManager.onTileRemoved();
TileLifecycleManager.setTileAdded(mContext, component, false);
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("night")) return new NightDisplayTile(this);
else if (tileSpec.equals("nfc")) return new NfcTile(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<String> 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<String> tiles = new ArrayList<String>();
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;
}
}