| /* |
| * 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)); |
| |
| Dependency.get(TunerService.class).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(); |
| Dependency.get(TunerService.class).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; |
| } |
| } |