Home | History | Annotate | Download | only in qs
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
      5  * except in compliance with the License. You may obtain a copy of the License at
      6  *
      7  *      http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the
     10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     11  * KIND, either express or implied. See the License for the specific language governing
     12  * permissions and limitations under the License.
     13  */
     14 
     15 package com.android.systemui.qs;
     16 
     17 import android.app.ActivityManager;
     18 import android.content.ComponentName;
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.content.res.Resources;
     22 import android.os.Handler;
     23 import android.os.UserHandle;
     24 import android.os.UserManager;
     25 import android.provider.Settings;
     26 import android.provider.Settings.Secure;
     27 import android.service.quicksettings.Tile;
     28 import android.text.TextUtils;
     29 import android.util.Log;
     30 
     31 import com.android.systemui.Dependency;
     32 import com.android.systemui.R;
     33 import com.android.systemui.plugins.PluginListener;
     34 import com.android.systemui.plugins.PluginManager;
     35 import com.android.systemui.plugins.qs.QSFactory;
     36 import com.android.systemui.plugins.qs.QSTileView;
     37 import com.android.systemui.plugins.qs.QSTile;
     38 import com.android.systemui.qs.external.CustomTile;
     39 import com.android.systemui.qs.external.TileLifecycleManager;
     40 import com.android.systemui.qs.external.TileServices;
     41 import com.android.systemui.qs.tileimpl.QSFactoryImpl;
     42 import com.android.systemui.statusbar.phone.AutoTileManager;
     43 import com.android.systemui.statusbar.phone.StatusBar;
     44 import com.android.systemui.statusbar.phone.StatusBarIconController;
     45 import com.android.systemui.tuner.TunerService;
     46 import com.android.systemui.tuner.TunerService.Tunable;
     47 
     48 import java.util.ArrayList;
     49 import java.util.Arrays;
     50 import java.util.Collection;
     51 import java.util.LinkedHashMap;
     52 import java.util.List;
     53 
     54 /** Platform implementation of the quick settings tile host **/
     55 public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory> {
     56     private static final String TAG = "QSTileHost";
     57     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     58 
     59     public static final String TILES_SETTING = Secure.QS_TILES;
     60 
     61     private final Context mContext;
     62     private final StatusBar mStatusBar;
     63     private final LinkedHashMap<String, QSTile> mTiles = new LinkedHashMap<>();
     64     protected final ArrayList<String> mTileSpecs = new ArrayList<>();
     65     private final TileServices mServices;
     66 
     67     private final List<Callback> mCallbacks = new ArrayList<>();
     68     private final AutoTileManager mAutoTiles;
     69     private final StatusBarIconController mIconController;
     70     private final ArrayList<QSFactory> mQsFactories = new ArrayList<>();
     71     private int mCurrentUser;
     72 
     73     public QSTileHost(Context context, StatusBar statusBar,
     74             StatusBarIconController iconController) {
     75         mIconController = iconController;
     76         mContext = context;
     77         mStatusBar = statusBar;
     78 
     79         mServices = new TileServices(this, Dependency.get(Dependency.BG_LOOPER));
     80 
     81         mQsFactories.add(new QSFactoryImpl(this));
     82         Dependency.get(PluginManager.class).addPluginListener(this, QSFactory.class, true);
     83 
     84         Dependency.get(TunerService.class).addTunable(this, TILES_SETTING);
     85         // AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
     86         mAutoTiles = new AutoTileManager(context, this);
     87     }
     88 
     89     public StatusBarIconController getIconController() {
     90         return mIconController;
     91     }
     92 
     93     public void destroy() {
     94         mTiles.values().forEach(tile -> tile.destroy());
     95         mAutoTiles.destroy();
     96         Dependency.get(TunerService.class).removeTunable(this);
     97         mServices.destroy();
     98         Dependency.get(PluginManager.class).removePluginListener(this);
     99     }
    100 
    101     @Override
    102     public void onPluginConnected(QSFactory plugin, Context pluginContext) {
    103         // Give plugins priority over creation so they can override if they wish.
    104         mQsFactories.add(0, plugin);
    105         String value = Dependency.get(TunerService.class).getValue(TILES_SETTING);
    106         // Force remove and recreate of all tiles.
    107         onTuningChanged(TILES_SETTING, "");
    108         onTuningChanged(TILES_SETTING, value);
    109     }
    110 
    111     @Override
    112     public void onPluginDisconnected(QSFactory plugin) {
    113         mQsFactories.remove(plugin);
    114         // Force remove and recreate of all tiles.
    115         String value = Dependency.get(TunerService.class).getValue(TILES_SETTING);
    116         onTuningChanged(TILES_SETTING, "");
    117         onTuningChanged(TILES_SETTING, value);
    118     }
    119 
    120     @Override
    121     public void addCallback(Callback callback) {
    122         mCallbacks.add(callback);
    123     }
    124 
    125     @Override
    126     public void removeCallback(Callback callback) {
    127         mCallbacks.remove(callback);
    128     }
    129 
    130     @Override
    131     public Collection<QSTile> getTiles() {
    132         return mTiles.values();
    133     }
    134 
    135     @Override
    136     public void warn(String message, Throwable t) {
    137         // already logged
    138     }
    139 
    140     @Override
    141     public void collapsePanels() {
    142         mStatusBar.postAnimateCollapsePanels();
    143     }
    144 
    145     @Override
    146     public void forceCollapsePanels() {
    147         mStatusBar.postAnimateForceCollapsePanels();
    148     }
    149 
    150     @Override
    151     public void openPanels() {
    152         mStatusBar.postAnimateOpenPanels();
    153     }
    154 
    155     @Override
    156     public Context getContext() {
    157         return mContext;
    158     }
    159 
    160 
    161     public TileServices getTileServices() {
    162         return mServices;
    163     }
    164 
    165     public int indexOf(String spec) {
    166         return mTileSpecs.indexOf(spec);
    167     }
    168 
    169     @Override
    170     public void onTuningChanged(String key, String newValue) {
    171         if (!TILES_SETTING.equals(key)) {
    172             return;
    173         }
    174         if (DEBUG) Log.d(TAG, "Recreating tiles");
    175         if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
    176             newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
    177         }
    178         final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
    179         int currentUser = ActivityManager.getCurrentUser();
    180         if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
    181         mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
    182                 tile -> {
    183                     if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
    184                     tile.getValue().destroy();
    185                 });
    186         final LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>();
    187         for (String tileSpec : tileSpecs) {
    188             QSTile tile = mTiles.get(tileSpec);
    189             if (tile != null && (!(tile instanceof CustomTile)
    190                     || ((CustomTile) tile).getUser() == currentUser)) {
    191                 if (tile.isAvailable()) {
    192                     if (DEBUG) Log.d(TAG, "Adding " + tile);
    193                     tile.removeCallbacks();
    194                     if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) {
    195                         tile.userSwitch(currentUser);
    196                     }
    197                     newTiles.put(tileSpec, tile);
    198                 } else {
    199                     tile.destroy();
    200                 }
    201             } else {
    202                 if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
    203                 try {
    204                     tile = createTile(tileSpec);
    205                     if (tile != null) {
    206                         if (tile.isAvailable()) {
    207                             tile.setTileSpec(tileSpec);
    208                             newTiles.put(tileSpec, tile);
    209                         } else {
    210                             tile.destroy();
    211                         }
    212                     }
    213                 } catch (Throwable t) {
    214                     Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
    215                 }
    216             }
    217         }
    218         mCurrentUser = currentUser;
    219         mTileSpecs.clear();
    220         mTileSpecs.addAll(tileSpecs);
    221         mTiles.clear();
    222         mTiles.putAll(newTiles);
    223         for (int i = 0; i < mCallbacks.size(); i++) {
    224             mCallbacks.get(i).onTilesChanged();
    225         }
    226     }
    227 
    228     @Override
    229     public void removeTile(String tileSpec) {
    230         ArrayList<String> specs = new ArrayList<>(mTileSpecs);
    231         specs.remove(tileSpec);
    232         Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING,
    233                 TextUtils.join(",", specs), ActivityManager.getCurrentUser());
    234     }
    235 
    236     public void addTile(String spec) {
    237         final String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
    238                 TILES_SETTING, ActivityManager.getCurrentUser());
    239         final List<String> tileSpecs = loadTileSpecs(mContext, setting);
    240         if (tileSpecs.contains(spec)) {
    241             return;
    242         }
    243         tileSpecs.add(spec);
    244         Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING,
    245                 TextUtils.join(",", tileSpecs), ActivityManager.getCurrentUser());
    246     }
    247 
    248     public void addTile(ComponentName tile) {
    249         List<String> newSpecs = new ArrayList<>(mTileSpecs);
    250         newSpecs.add(0, CustomTile.toSpec(tile));
    251         changeTiles(mTileSpecs, newSpecs);
    252     }
    253 
    254     public void removeTile(ComponentName tile) {
    255         List<String> newSpecs = new ArrayList<>(mTileSpecs);
    256         newSpecs.remove(CustomTile.toSpec(tile));
    257         changeTiles(mTileSpecs, newSpecs);
    258     }
    259 
    260     public void changeTiles(List<String> previousTiles, List<String> newTiles) {
    261         final int NP = previousTiles.size();
    262         final int NA = newTiles.size();
    263         for (int i = 0; i < NP; i++) {
    264             String tileSpec = previousTiles.get(i);
    265             if (!tileSpec.startsWith(CustomTile.PREFIX)) continue;
    266             if (!newTiles.contains(tileSpec)) {
    267                 ComponentName component = CustomTile.getComponentFromSpec(tileSpec);
    268                 Intent intent = new Intent().setComponent(component);
    269                 TileLifecycleManager lifecycleManager = new TileLifecycleManager(new Handler(),
    270                         mContext, mServices, new Tile(), intent,
    271                         new UserHandle(ActivityManager.getCurrentUser()));
    272                 lifecycleManager.onStopListening();
    273                 lifecycleManager.onTileRemoved();
    274                 TileLifecycleManager.setTileAdded(mContext, component, false);
    275                 lifecycleManager.flushMessagesAndUnbind();
    276             }
    277         }
    278         if (DEBUG) Log.d(TAG, "saveCurrentTiles " + newTiles);
    279         Secure.putStringForUser(getContext().getContentResolver(), QSTileHost.TILES_SETTING,
    280                 TextUtils.join(",", newTiles), ActivityManager.getCurrentUser());
    281     }
    282 
    283     public QSTile createTile(String tileSpec) {
    284         for (int i = 0; i < mQsFactories.size(); i++) {
    285             QSTile t = mQsFactories.get(i).createTile(tileSpec);
    286             if (t != null) {
    287                 return t;
    288             }
    289         }
    290         return null;
    291     }
    292 
    293     public QSTileView createTileView(QSTile tile, boolean collapsedView) {
    294         for (int i = 0; i < mQsFactories.size(); i++) {
    295             QSTileView view = mQsFactories.get(i).createTileView(tile, collapsedView);
    296             if (view != null) {
    297                 return view;
    298             }
    299         }
    300         throw new RuntimeException("Default factory didn't create view for " + tile.getTileSpec());
    301     }
    302 
    303     protected List<String> loadTileSpecs(Context context, String tileList) {
    304         final Resources res = context.getResources();
    305         final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
    306         if (tileList == null) {
    307             tileList = res.getString(R.string.quick_settings_tiles);
    308             if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);
    309         } else {
    310             if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList);
    311         }
    312         final ArrayList<String> tiles = new ArrayList<String>();
    313         boolean addedDefault = false;
    314         for (String tile : tileList.split(",")) {
    315             tile = tile.trim();
    316             if (tile.isEmpty()) continue;
    317             if (tile.equals("default")) {
    318                 if (!addedDefault) {
    319                     tiles.addAll(Arrays.asList(defaultTileList.split(",")));
    320                     addedDefault = true;
    321                 }
    322             } else {
    323                 tiles.add(tile);
    324             }
    325         }
    326         return tiles;
    327     }
    328 }
    329