• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
QSTileHost(Context context, StatusBar statusBar, StatusBarIconController iconController)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 
getIconController()89     public StatusBarIconController getIconController() {
90         return mIconController;
91     }
92 
destroy()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
onPluginConnected(QSFactory plugin, Context pluginContext)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
onPluginDisconnected(QSFactory plugin)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
addCallback(Callback callback)121     public void addCallback(Callback callback) {
122         mCallbacks.add(callback);
123     }
124 
125     @Override
removeCallback(Callback callback)126     public void removeCallback(Callback callback) {
127         mCallbacks.remove(callback);
128     }
129 
130     @Override
getTiles()131     public Collection<QSTile> getTiles() {
132         return mTiles.values();
133     }
134 
135     @Override
warn(String message, Throwable t)136     public void warn(String message, Throwable t) {
137         // already logged
138     }
139 
140     @Override
collapsePanels()141     public void collapsePanels() {
142         mStatusBar.postAnimateCollapsePanels();
143     }
144 
145     @Override
forceCollapsePanels()146     public void forceCollapsePanels() {
147         mStatusBar.postAnimateForceCollapsePanels();
148     }
149 
150     @Override
openPanels()151     public void openPanels() {
152         mStatusBar.postAnimateOpenPanels();
153     }
154 
155     @Override
getContext()156     public Context getContext() {
157         return mContext;
158     }
159 
160 
getTileServices()161     public TileServices getTileServices() {
162         return mServices;
163     }
164 
indexOf(String spec)165     public int indexOf(String spec) {
166         return mTileSpecs.indexOf(spec);
167     }
168 
169     @Override
onTuningChanged(String key, String newValue)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
removeTile(String tileSpec)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 
addTile(String spec)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 
addTile(ComponentName tile)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 
removeTile(ComponentName tile)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 
changeTiles(List<String> previousTiles, List<String> newTiles)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 
createTile(String tileSpec)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 
createTileView(QSTile tile, boolean collapsedView)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 
loadTileSpecs(Context context, String tileList)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