blob: dfc3e66d8d98eb402516c8ab6cc4f40a64281018 [file] [log] [blame]
John Spurlockaf8d6c42014-05-07 17:49:08 -04001/*
Jason Monke5b770e2017-03-03 21:49:29 -05002 * Copyright (C) 2017 The Android Open Source Project
John Spurlockaf8d6c42014-05-07 17:49:08 -04003 *
Jason Monke5b770e2017-03-03 21:49:29 -05004 * 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
John Spurlockaf8d6c42014-05-07 17:49:08 -04006 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
Jason Monke5b770e2017-03-03 21:49:29 -05009 * 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.
John Spurlockaf8d6c42014-05-07 17:49:08 -040013 */
14
Jason Monke5b770e2017-03-03 21:49:29 -050015package com.android.systemui.qs;
John Spurlockaf8d6c42014-05-07 17:49:08 -040016
Jason Monkba2318e2015-12-08 09:04:23 -050017import android.app.ActivityManager;
Jason Monk7e53f202016-01-28 10:40:20 -050018import android.content.ComponentName;
John Spurlockaf8d6c42014-05-07 17:49:08 -040019import android.content.Context;
20import android.content.Intent;
John Spurlockbceed062014-08-10 18:04:16 -040021import android.content.res.Resources;
Jason Monk7e53f202016-01-28 10:40:20 -050022import android.os.Handler;
Jason Monk30b64fa2018-12-27 13:19:34 -050023import android.os.Looper;
Jason Monk7e53f202016-01-28 10:40:20 -050024import android.os.UserHandle;
Sudheer Shankaad790492016-06-03 10:48:27 -070025import android.os.UserManager;
Jason Monkba2318e2015-12-08 09:04:23 -050026import android.provider.Settings;
Jason Monk7e53f202016-01-28 10:40:20 -050027import android.provider.Settings.Secure;
Jason Monka3453b8b2016-06-17 12:42:59 -040028import android.service.quicksettings.Tile;
Jason Monkba2318e2015-12-08 09:04:23 -050029import android.text.TextUtils;
John Spurlockbceed062014-08-10 18:04:16 -040030import android.util.Log;
Winsonc0d70582016-01-29 10:24:39 -080031
Jason Monk9c7844c2017-01-18 15:21:53 -050032import com.android.systemui.Dependency;
John Spurlockbceed062014-08-10 18:04:16 -040033import com.android.systemui.R;
Jason Monk5d577202018-12-26 15:43:06 -050034import com.android.systemui.SysUiServiceProvider;
Jason Monk702e2eb2017-03-03 16:53:44 -050035import com.android.systemui.plugins.PluginListener;
Jason Monk702e2eb2017-03-03 16:53:44 -050036import com.android.systemui.plugins.qs.QSFactory;
Jason Monk702e2eb2017-03-03 16:53:44 -050037import com.android.systemui.plugins.qs.QSTile;
Gus Prevasab336792018-11-14 13:52:20 -050038import com.android.systemui.plugins.qs.QSTileView;
Jason Monkd5a204f2015-12-21 08:50:01 -050039import com.android.systemui.qs.external.CustomTile;
Jason Monk7e53f202016-01-28 10:40:20 -050040import com.android.systemui.qs.external.TileLifecycleManager;
Jason Monkd5a204f2015-12-21 08:50:01 -050041import com.android.systemui.qs.external.TileServices;
Jason Monk702e2eb2017-03-03 16:53:44 -050042import com.android.systemui.qs.tileimpl.QSFactoryImpl;
Gus Prevasab336792018-11-14 13:52:20 -050043import com.android.systemui.shared.plugins.PluginManager;
Jason Monke5b770e2017-03-03 21:49:29 -050044import com.android.systemui.statusbar.phone.AutoTileManager;
45import com.android.systemui.statusbar.phone.StatusBar;
46import com.android.systemui.statusbar.phone.StatusBarIconController;
Jason Monk5e745172015-06-02 19:14:44 -040047import com.android.systemui.tuner.TunerService;
48import com.android.systemui.tuner.TunerService.Tunable;
John Spurlockaf8d6c42014-05-07 17:49:08 -040049
50import java.util.ArrayList;
John Spurlockbceed062014-08-10 18:04:16 -040051import java.util.Arrays;
52import java.util.Collection;
53import java.util.LinkedHashMap;
John Spurlockaf8d6c42014-05-07 17:49:08 -040054import java.util.List;
Amin Shaikha062beb2018-07-25 11:21:54 -040055import java.util.function.Predicate;
John Spurlockaf8d6c42014-05-07 17:49:08 -040056
Jason Monk5d577202018-12-26 15:43:06 -050057import javax.inject.Inject;
58import javax.inject.Named;
Jason Monka26641f2018-12-27 14:20:53 -050059import javax.inject.Provider;
Jason Monk5d577202018-12-26 15:43:06 -050060import javax.inject.Singleton;
61
John Spurlockaf8d6c42014-05-07 17:49:08 -040062/** Platform implementation of the quick settings tile host **/
Jason Monk5d577202018-12-26 15:43:06 -050063@Singleton
Jason Monk702e2eb2017-03-03 16:53:44 -050064public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory> {
John Spurlockbceed062014-08-10 18:04:16 -040065 private static final String TAG = "QSTileHost";
66 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
67
Jason Monkbaade752016-08-25 15:57:14 -040068 public static final String TILES_SETTING = Secure.QS_TILES;
John Spurlockaf8d6c42014-05-07 17:49:08 -040069
70 private final Context mContext;
Jason Monk702e2eb2017-03-03 16:53:44 -050071 private final LinkedHashMap<String, QSTile> mTiles = new LinkedHashMap<>();
Jason Monk5e745172015-06-02 19:14:44 -040072 protected final ArrayList<String> mTileSpecs = new ArrayList<>();
Jason Monkd5a204f2015-12-21 08:50:01 -050073 private final TileServices mServices;
Jason Monk30b64fa2018-12-27 13:19:34 -050074 private final TunerService mTunerService;
75 private final PluginManager mPluginManager;
John Spurlockaf8d6c42014-05-07 17:49:08 -040076
Jason Monkbbadff82015-11-06 15:47:26 -050077 private final List<Callback> mCallbacks = new ArrayList<>();
Jason Monka26641f2018-12-27 14:20:53 -050078 private AutoTileManager mAutoTiles;
Jason Monk9c7844c2017-01-18 15:21:53 -050079 private final StatusBarIconController mIconController;
Jason Monk702e2eb2017-03-03 16:53:44 -050080 private final ArrayList<QSFactory> mQsFactories = new ArrayList<>();
Jason Monk1ffa11b2016-03-08 14:44:23 -050081 private int mCurrentUser;
Jason Monk5d577202018-12-26 15:43:06 -050082 private StatusBar mStatusBar;
John Spurlockbceed062014-08-10 18:04:16 -040083
Jason Monk5d577202018-12-26 15:43:06 -050084 @Inject
85 public QSTileHost(Context context,
86 StatusBarIconController iconController,
87 QSFactoryImpl defaultFactory,
Jason Monk30b64fa2018-12-27 13:19:34 -050088 @Named(Dependency.MAIN_HANDLER_NAME) Handler mainHandler,
89 @Named(Dependency.BG_LOOPER_NAME) Looper bgLooper,
90 PluginManager pluginManager,
Jason Monka26641f2018-12-27 14:20:53 -050091 TunerService tunerService,
92 Provider<AutoTileManager> autoTiles) {
Jason Monk9c7844c2017-01-18 15:21:53 -050093 mIconController = iconController;
John Spurlockaf8d6c42014-05-07 17:49:08 -040094 mContext = context;
Jason Monk30b64fa2018-12-27 13:19:34 -050095 mTunerService = tunerService;
96 mPluginManager = pluginManager;
John Spurlockaf8d6c42014-05-07 17:49:08 -040097
Jason Monk30b64fa2018-12-27 13:19:34 -050098 mServices = new TileServices(this, bgLooper);
Jason Monkd5a204f2015-12-21 08:50:01 -050099
Jason Monk5d577202018-12-26 15:43:06 -0500100 defaultFactory.setHost(this);
101 mQsFactories.add(defaultFactory);
Jason Monk30b64fa2018-12-27 13:19:34 -0500102 pluginManager.addPluginListener(this, QSFactory.class, true);
Jason Monk702e2eb2017-03-03 16:53:44 -0500103
Jason Monk5d577202018-12-26 15:43:06 -0500104 mainHandler.post(() -> {
105 // This is technically a hack to avoid circular dependency of
106 // QSTileHost -> XXXTile -> QSTileHost. Posting ensures creation
107 // finishes before creating any tiles.
Jason Monk30b64fa2018-12-27 13:19:34 -0500108 tunerService.addTunable(this, TILES_SETTING);
Jason Monka26641f2018-12-27 14:20:53 -0500109 // AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
110 mAutoTiles = autoTiles.get();
Jason Monk5d577202018-12-26 15:43:06 -0500111 });
Jason Monk5e745172015-06-02 19:14:44 -0400112 }
John Spurlockbceed062014-08-10 18:04:16 -0400113
Jason Monk9c7844c2017-01-18 15:21:53 -0500114 public StatusBarIconController getIconController() {
115 return mIconController;
Jason Monkca894a02016-01-12 15:30:22 -0500116 }
117
Jason Monk5e745172015-06-02 19:14:44 -0400118 public void destroy() {
Jason Monk88529052016-11-04 13:29:58 -0400119 mTiles.values().forEach(tile -> tile.destroy());
Jason Monkc3f42c12016-02-05 12:33:13 -0500120 mAutoTiles.destroy();
Jason Monk30b64fa2018-12-27 13:19:34 -0500121 mTunerService.removeTunable(this);
Jason Monk88529052016-11-04 13:29:58 -0400122 mServices.destroy();
Jason Monk30b64fa2018-12-27 13:19:34 -0500123 mPluginManager.removePluginListener(this);
Jason Monk702e2eb2017-03-03 16:53:44 -0500124 }
125
126 @Override
127 public void onPluginConnected(QSFactory plugin, Context pluginContext) {
128 // Give plugins priority over creation so they can override if they wish.
129 mQsFactories.add(0, plugin);
Jason Monk30b64fa2018-12-27 13:19:34 -0500130 String value = mTunerService.getValue(TILES_SETTING);
Jason Monk702e2eb2017-03-03 16:53:44 -0500131 // Force remove and recreate of all tiles.
132 onTuningChanged(TILES_SETTING, "");
133 onTuningChanged(TILES_SETTING, value);
134 }
135
136 @Override
137 public void onPluginDisconnected(QSFactory plugin) {
138 mQsFactories.remove(plugin);
139 // Force remove and recreate of all tiles.
Jason Monk30b64fa2018-12-27 13:19:34 -0500140 String value = mTunerService.getValue(TILES_SETTING);
Jason Monk702e2eb2017-03-03 16:53:44 -0500141 onTuningChanged(TILES_SETTING, "");
142 onTuningChanged(TILES_SETTING, value);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400143 }
144
145 @Override
Jason Monkbbadff82015-11-06 15:47:26 -0500146 public void addCallback(Callback callback) {
147 mCallbacks.add(callback);
John Spurlockbceed062014-08-10 18:04:16 -0400148 }
149
150 @Override
Jason Monk46dbfb42016-02-25 14:59:20 -0500151 public void removeCallback(Callback callback) {
152 mCallbacks.remove(callback);
153 }
154
155 @Override
Jason Monk702e2eb2017-03-03 16:53:44 -0500156 public Collection<QSTile> getTiles() {
John Spurlockbceed062014-08-10 18:04:16 -0400157 return mTiles.values();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400158 }
159
160 @Override
John Spurlockaf8d6c42014-05-07 17:49:08 -0400161 public void warn(String message, Throwable t) {
162 // already logged
163 }
164
165 @Override
166 public void collapsePanels() {
Jason Monk5d577202018-12-26 15:43:06 -0500167 if (mStatusBar == null) {
168 mStatusBar = SysUiServiceProvider.getComponent(mContext, StatusBar.class);
169 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400170 mStatusBar.postAnimateCollapsePanels();
171 }
172
173 @Override
Yoshinori Hiranoc673fbc2017-04-14 11:29:38 +0900174 public void forceCollapsePanels() {
Jason Monk5d577202018-12-26 15:43:06 -0500175 if (mStatusBar == null) {
176 mStatusBar = SysUiServiceProvider.getComponent(mContext, StatusBar.class);
177 }
Yoshinori Hiranoc673fbc2017-04-14 11:29:38 +0900178 mStatusBar.postAnimateForceCollapsePanels();
179 }
180
181 @Override
Jason Monkba2318e2015-12-08 09:04:23 -0500182 public void openPanels() {
Jason Monk5d577202018-12-26 15:43:06 -0500183 if (mStatusBar == null) {
184 mStatusBar = SysUiServiceProvider.getComponent(mContext, StatusBar.class);
185 }
Jason Monkba2318e2015-12-08 09:04:23 -0500186 mStatusBar.postAnimateOpenPanels();
187 }
188
189 @Override
John Spurlockaf8d6c42014-05-07 17:49:08 -0400190 public Context getContext() {
191 return mContext;
192 }
193
Jason Monkbbadff82015-11-06 15:47:26 -0500194
Jason Monkd5a204f2015-12-21 08:50:01 -0500195 public TileServices getTileServices() {
196 return mServices;
197 }
198
Jason Monk8c09ac72017-03-16 11:53:40 -0400199 public int indexOf(String spec) {
200 return mTileSpecs.indexOf(spec);
201 }
202
Jason Monk5e745172015-06-02 19:14:44 -0400203 @Override
204 public void onTuningChanged(String key, String newValue) {
205 if (!TILES_SETTING.equals(key)) {
206 return;
207 }
John Spurlockbceed062014-08-10 18:04:16 -0400208 if (DEBUG) Log.d(TAG, "Recreating tiles");
Sudheer Shankaad790492016-06-03 10:48:27 -0700209 if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
210 newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
211 }
Jason Monkbbadff82015-11-06 15:47:26 -0500212 final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
Jason Monk1ffa11b2016-03-08 14:44:23 -0500213 int currentUser = ActivityManager.getCurrentUser();
214 if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
Jason Monk88529052016-11-04 13:29:58 -0400215 mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
216 tile -> {
217 if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
218 tile.getValue().destroy();
219 });
Jason Monk702e2eb2017-03-03 16:53:44 -0500220 final LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>();
John Spurlockbceed062014-08-10 18:04:16 -0400221 for (String tileSpec : tileSpecs) {
Jason Monk702e2eb2017-03-03 16:53:44 -0500222 QSTile tile = mTiles.get(tileSpec);
Jason Monk1ffa11b2016-03-08 14:44:23 -0500223 if (tile != null && (!(tile instanceof CustomTile)
224 || ((CustomTile) tile).getUser() == currentUser)) {
Akitaka Kimuraefd1c542016-09-27 20:06:00 +0900225 if (tile.isAvailable()) {
226 if (DEBUG) Log.d(TAG, "Adding " + tile);
227 tile.removeCallbacks();
Jason Monkf120cf32016-12-06 11:34:12 -0500228 if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) {
229 tile.userSwitch(currentUser);
230 }
Akitaka Kimuraefd1c542016-09-27 20:06:00 +0900231 newTiles.put(tileSpec, tile);
232 } else {
233 tile.destroy();
Justin Klaassendd32d902016-07-31 10:25:36 -0700234 }
John Spurlockbceed062014-08-10 18:04:16 -0400235 } else {
236 if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
237 try {
Jason Monk1ffa11b2016-03-08 14:44:23 -0500238 tile = createTile(tileSpec);
Jason Monk88529052016-11-04 13:29:58 -0400239 if (tile != null) {
240 if (tile.isAvailable()) {
241 tile.setTileSpec(tileSpec);
242 newTiles.put(tileSpec, tile);
243 } else {
244 tile.destroy();
245 }
Jason Monkba2318e2015-12-08 09:04:23 -0500246 }
John Spurlockbceed062014-08-10 18:04:16 -0400247 } catch (Throwable t) {
248 Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
249 }
250 }
251 }
Jason Monk1ffa11b2016-03-08 14:44:23 -0500252 mCurrentUser = currentUser;
Jason Monk11a77442015-05-12 19:29:02 -0400253 mTileSpecs.clear();
254 mTileSpecs.addAll(tileSpecs);
John Spurlockbceed062014-08-10 18:04:16 -0400255 mTiles.clear();
256 mTiles.putAll(newTiles);
Jason Monkbbadff82015-11-06 15:47:26 -0500257 for (int i = 0; i < mCallbacks.size(); i++) {
258 mCallbacks.get(i).onTilesChanged();
John Spurlockbceed062014-08-10 18:04:16 -0400259 }
260 }
261
Jason Monkbbadff82015-11-06 15:47:26 -0500262 @Override
Amin Shaikha062beb2018-07-25 11:21:54 -0400263 public void removeTile(String spec) {
264 changeTileSpecs(tileSpecs-> tileSpecs.remove(spec));
Jason Monkba2318e2015-12-08 09:04:23 -0500265 }
266
arangelov5fe95982018-08-17 18:43:31 +0100267 @Override
268 public void unmarkTileAsAutoAdded(String spec) {
Jason Monka26641f2018-12-27 14:20:53 -0500269 if (mAutoTiles != null) mAutoTiles.unmarkTileAsAutoAdded(spec);
arangelov5fe95982018-08-17 18:43:31 +0100270 }
271
Jason Monkc3f42c12016-02-05 12:33:13 -0500272 public void addTile(String spec) {
Amin Shaikha062beb2018-07-25 11:21:54 -0400273 changeTileSpecs(tileSpecs-> tileSpecs.add(spec));
274 }
275
276 private void changeTileSpecs(Predicate<List<String>> changeFunction) {
Ricky Waib96c1eac2016-06-02 11:42:16 +0100277 final String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
Amin Shaikha062beb2018-07-25 11:21:54 -0400278 TILES_SETTING, ActivityManager.getCurrentUser());
Ricky Waib96c1eac2016-06-02 11:42:16 +0100279 final List<String> tileSpecs = loadTileSpecs(mContext, setting);
Amin Shaikha062beb2018-07-25 11:21:54 -0400280 if (changeFunction.test(tileSpecs)) {
281 Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING,
Ricky Waib96c1eac2016-06-02 11:42:16 +0100282 TextUtils.join(",", tileSpecs), ActivityManager.getCurrentUser());
Amin Shaikha062beb2018-07-25 11:21:54 -0400283 }
Jason Monkc3f42c12016-02-05 12:33:13 -0500284 }
285
Jason Monk7e53f202016-01-28 10:40:20 -0500286 public void addTile(ComponentName tile) {
287 List<String> newSpecs = new ArrayList<>(mTileSpecs);
288 newSpecs.add(0, CustomTile.toSpec(tile));
289 changeTiles(mTileSpecs, newSpecs);
290 }
291
292 public void removeTile(ComponentName tile) {
293 List<String> newSpecs = new ArrayList<>(mTileSpecs);
294 newSpecs.remove(CustomTile.toSpec(tile));
295 changeTiles(mTileSpecs, newSpecs);
296 }
297
298 public void changeTiles(List<String> previousTiles, List<String> newTiles) {
299 final int NP = previousTiles.size();
300 final int NA = newTiles.size();
301 for (int i = 0; i < NP; i++) {
302 String tileSpec = previousTiles.get(i);
303 if (!tileSpec.startsWith(CustomTile.PREFIX)) continue;
304 if (!newTiles.contains(tileSpec)) {
Jason Monka3453b8b2016-06-17 12:42:59 -0400305 ComponentName component = CustomTile.getComponentFromSpec(tileSpec);
306 Intent intent = new Intent().setComponent(component);
Jason Monk7e53f202016-01-28 10:40:20 -0500307 TileLifecycleManager lifecycleManager = new TileLifecycleManager(new Handler(),
Jason Monkee68fd82016-06-23 13:12:23 -0400308 mContext, mServices, new Tile(), intent,
Jason Monka3453b8b2016-06-17 12:42:59 -0400309 new UserHandle(ActivityManager.getCurrentUser()));
Jason Monk7e53f202016-01-28 10:40:20 -0500310 lifecycleManager.onStopListening();
311 lifecycleManager.onTileRemoved();
Jason Monkbaade752016-08-25 15:57:14 -0400312 TileLifecycleManager.setTileAdded(mContext, component, false);
Jason Monk7e53f202016-01-28 10:40:20 -0500313 lifecycleManager.flushMessagesAndUnbind();
314 }
315 }
316 if (DEBUG) Log.d(TAG, "saveCurrentTiles " + newTiles);
317 Secure.putStringForUser(getContext().getContentResolver(), QSTileHost.TILES_SETTING,
318 TextUtils.join(",", newTiles), ActivityManager.getCurrentUser());
319 }
320
Jason Monk702e2eb2017-03-03 16:53:44 -0500321 public QSTile createTile(String tileSpec) {
322 for (int i = 0; i < mQsFactories.size(); i++) {
323 QSTile t = mQsFactories.get(i).createTile(tileSpec);
324 if (t != null) {
325 return t;
326 }
Jason Monk2b37190f2016-01-06 10:19:04 -0500327 }
Jason Monk702e2eb2017-03-03 16:53:44 -0500328 return null;
329 }
330
331 public QSTileView createTileView(QSTile tile, boolean collapsedView) {
332 for (int i = 0; i < mQsFactories.size(); i++) {
333 QSTileView view = mQsFactories.get(i).createTileView(tile, collapsedView);
334 if (view != null) {
335 return view;
336 }
337 }
338 throw new RuntimeException("Default factory didn't create view for " + tile.getTileSpec());
John Spurlockbceed062014-08-10 18:04:16 -0400339 }
340
Amin Shaikha062beb2018-07-25 11:21:54 -0400341 protected static List<String> loadTileSpecs(Context context, String tileList) {
Jason Monkbbadff82015-11-06 15:47:26 -0500342 final Resources res = context.getResources();
John Spurlockbceed062014-08-10 18:04:16 -0400343 final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
Fabian Kozynski7f370dd2018-12-05 10:41:07 -0500344 if (TextUtils.isEmpty(tileList)) {
John Spurlockbceed062014-08-10 18:04:16 -0400345 tileList = res.getString(R.string.quick_settings_tiles);
346 if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);
347 } else {
348 if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList);
349 }
350 final ArrayList<String> tiles = new ArrayList<String>();
351 boolean addedDefault = false;
352 for (String tile : tileList.split(",")) {
353 tile = tile.trim();
354 if (tile.isEmpty()) continue;
355 if (tile.equals("default")) {
356 if (!addedDefault) {
357 tiles.addAll(Arrays.asList(defaultTileList.split(",")));
358 addedDefault = true;
359 }
360 } else {
361 tiles.add(tile);
362 }
363 }
364 return tiles;
365 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400366}