blob: 70062492c1624f1c7553700030f4b9cae4142cff [file] [log] [blame]
Jason Monk62b63a02016-02-02 15:15:31 -05001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.qs.customize;
18
Jason Monkb53b6c52016-02-24 17:25:49 -050019import android.Manifest.permission;
Jason Monk62b63a02016-02-02 15:15:31 -050020import android.app.ActivityManager;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
26import android.graphics.drawable.Drawable;
Dan Sandler4d90d1e2018-03-23 16:29:06 -040027import android.os.Build;
Jason Monk62b63a02016-02-02 15:15:31 -050028import android.os.Handler;
Fabian Kozynskibb3ff872018-11-16 14:51:44 -050029import android.provider.Settings;
Fabian Kozynski7753f3e2019-03-01 13:56:26 -050030import android.service.quicksettings.Tile;
Jason Monk62b63a02016-02-02 15:15:31 -050031import android.service.quicksettings.TileService;
Amin Shaikhf7250472018-03-21 17:03:37 -040032import android.text.TextUtils;
Amin Shaikhc5b1bc82018-03-21 08:13:47 -040033import android.util.ArraySet;
Jason Monk50a352b2016-06-13 12:43:47 -040034import android.widget.Button;
35
Jason Monk9c7844c2017-01-18 15:21:53 -050036import com.android.systemui.Dependency;
Jason Monk62b63a02016-02-02 15:15:31 -050037import com.android.systemui.R;
Jason Monk702e2eb2017-03-03 16:53:44 -050038import com.android.systemui.plugins.qs.QSTile;
39import com.android.systemui.plugins.qs.QSTile.State;
Jason Monke5b770e2017-03-03 21:49:29 -050040import com.android.systemui.qs.QSTileHost;
Amin Shaikhc5b1bc82018-03-21 08:13:47 -040041import com.android.systemui.qs.external.CustomTile;
42import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
Dan Sandler4d90d1e2018-03-23 16:29:06 -040043import com.android.systemui.util.leak.GarbageMonitor;
Jason Monk62b63a02016-02-02 15:15:31 -050044
45import java.util.ArrayList;
Dan Sandler4d90d1e2018-03-23 16:29:06 -040046import java.util.Arrays;
Jason Monk62b63a02016-02-02 15:15:31 -050047import java.util.Collection;
48import java.util.List;
49
50public class TileQueryHelper {
Jason Monk62b63a02016-02-02 15:15:31 -050051 private static final String TAG = "TileQueryHelper";
52
53 private final ArrayList<TileInfo> mTiles = new ArrayList<>();
Amin Shaikhc5b1bc82018-03-21 08:13:47 -040054 private final ArraySet<String> mSpecs = new ArraySet<>();
55 private final Handler mBgHandler;
56 private final Handler mMainHandler;
Jason Monk62b63a02016-02-02 15:15:31 -050057 private final Context mContext;
Evan Laird6b284732017-02-28 17:27:04 -050058 private final TileStateListener mListener;
Jason Monk62b63a02016-02-02 15:15:31 -050059
Amin Shaikhc5b1bc82018-03-21 08:13:47 -040060 private boolean mFinished;
61
62 public TileQueryHelper(Context context, TileStateListener listener) {
Jason Monk62b63a02016-02-02 15:15:31 -050063 mContext = context;
Evan Laird6b284732017-02-28 17:27:04 -050064 mListener = listener;
Amin Shaikhc5b1bc82018-03-21 08:13:47 -040065 mBgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
66 mMainHandler = Dependency.get(Dependency.MAIN_HANDLER);
67 }
68
69 public void queryTiles(QSTileHost host) {
70 mTiles.clear();
71 mSpecs.clear();
72 mFinished = false;
73 // Enqueue jobs to fetch every system tile and then ever package tile.
Fabian Kozynskibb3ff872018-11-16 14:51:44 -050074 addCurrentAndStockTiles(host);
75
Amin Shaikhc5b1bc82018-03-21 08:13:47 -040076 addPackageTiles(host);
Jason Monk62b63a02016-02-02 15:15:31 -050077 }
78
Amin Shaikhc5b1bc82018-03-21 08:13:47 -040079 public boolean isFinished() {
80 return mFinished;
Evan Laird6b284732017-02-28 17:27:04 -050081 }
82
Fabian Kozynskibb3ff872018-11-16 14:51:44 -050083 private void addCurrentAndStockTiles(QSTileHost host) {
84 String stock = mContext.getString(R.string.quick_settings_tiles_stock);
85 String current = Settings.Secure.getString(mContext.getContentResolver(),
86 Settings.Secure.QS_TILES);
Dan Sandler4d90d1e2018-03-23 16:29:06 -040087 final ArrayList<String> possibleTiles = new ArrayList<>();
Fabian Kozynskibb3ff872018-11-16 14:51:44 -050088 if (current != null) {
89 // The setting QS_TILES is not populated immediately upon Factory Reset
90 possibleTiles.addAll(Arrays.asList(current.split(",")));
Fabian Kozynski565f2582018-12-19 17:39:53 -050091 } else {
92 current = "";
Fabian Kozynskibb3ff872018-11-16 14:51:44 -050093 }
94 String[] stockSplit = stock.split(",");
95 for (String spec : stockSplit) {
96 if (!current.contains(spec)) {
97 possibleTiles.add(spec);
98 }
99 }
100 if (Build.IS_DEBUGGABLE && !current.contains(GarbageMonitor.MemoryTile.TILE_SPEC)) {
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400101 possibleTiles.add(GarbageMonitor.MemoryTile.TILE_SPEC);
102 }
103
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400104 final ArrayList<QSTile> tilesToAdd = new ArrayList<>();
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400105 for (String spec : possibleTiles) {
Fabian Kozynskibb3ff872018-11-16 14:51:44 -0500106 // Only add current and stock tiles that can be created from QSFactoryImpl
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400107 final QSTile tile = host.createTile(spec);
Yuta Yamada99fd2772016-10-06 17:40:19 +0900108 if (tile == null) {
109 continue;
110 } else if (!tile.isAvailable()) {
111 tile.destroy();
Jason Monk62b63a02016-02-02 15:15:31 -0500112 continue;
113 }
Jason Monke5107a32016-05-31 15:40:58 -0400114 tile.setListening(this, true);
Jason Monk62b63a02016-02-02 15:15:31 -0500115 tile.refreshState();
Jason Monke5107a32016-05-31 15:40:58 -0400116 tile.setListening(this, false);
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400117 tile.setTileSpec(spec);
118 tilesToAdd.add(tile);
Jason Monk62b63a02016-02-02 15:15:31 -0500119 }
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400120
121 mBgHandler.post(() -> {
122 for (QSTile tile : tilesToAdd) {
123 final QSTile.State state = tile.getState().copy();
124 // Ignore the current state and get the generic label instead.
125 state.label = tile.getTileLabel();
126 tile.destroy();
127 addTile(tile.getTileSpec(), null, state, true);
128 }
129 notifyTilesChanged(false);
130 });
Evan Laird6b284732017-02-28 17:27:04 -0500131 }
132
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400133 private void addPackageTiles(final QSTileHost host) {
134 mBgHandler.post(() -> {
135 Collection<QSTile> params = host.getTiles();
Evan Laird6b284732017-02-28 17:27:04 -0500136 PackageManager pm = mContext.getPackageManager();
137 List<ResolveInfo> services = pm.queryIntentServicesAsUser(
138 new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser());
139 String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock);
140
141 for (ResolveInfo info : services) {
142 String packageName = info.serviceInfo.packageName;
143 ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
144
145 // Don't include apps that are a part of the default tile set.
146 if (stockTiles.contains(componentName.flattenToString())) {
147 continue;
148 }
149
150 final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm);
151 String spec = CustomTile.toSpec(componentName);
152 State state = getState(params, spec);
153 if (state != null) {
154 addTile(spec, appLabel, state, false);
155 continue;
156 }
157 if (info.serviceInfo.icon == 0 && info.serviceInfo.applicationInfo.icon == 0) {
158 continue;
159 }
160 Drawable icon = info.serviceInfo.loadIcon(pm);
161 if (!permission.BIND_QUICK_SETTINGS_TILE.equals(info.serviceInfo.permission)) {
162 continue;
163 }
164 if (icon == null) {
165 continue;
166 }
167 icon.mutate();
168 icon.setTint(mContext.getColor(android.R.color.white));
169 CharSequence label = info.serviceInfo.loadLabel(pm);
Fabian Kozynski7753f3e2019-03-01 13:56:26 -0500170 createStateAndAddTile(spec, icon, label != null ? label.toString() : "null",
171 appLabel);
Jason Monk62b63a02016-02-02 15:15:31 -0500172 }
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400173
174 notifyTilesChanged(true);
175 });
176 }
177
178 private void notifyTilesChanged(final boolean finished) {
179 final ArrayList<TileInfo> tilesToReturn = new ArrayList<>(mTiles);
180 mMainHandler.post(() -> {
181 mListener.onTilesChanged(tilesToReturn);
182 mFinished = finished;
Jason Monk62b63a02016-02-02 15:15:31 -0500183 });
184 }
185
Jason Monke5b770e2017-03-03 21:49:29 -0500186 private State getState(Collection<QSTile> tiles, String spec) {
187 for (QSTile tile : tiles) {
Evan Laird6b284732017-02-28 17:27:04 -0500188 if (spec.equals(tile.getTileSpec())) {
Jason Monk702e2eb2017-03-03 16:53:44 -0500189 return tile.getState().copy();
Evan Laird6b284732017-02-28 17:27:04 -0500190 }
191 }
192 return null;
Jason Monk62b63a02016-02-02 15:15:31 -0500193 }
194
Jason Monk0b349ad2016-04-04 11:18:03 -0400195 private void addTile(String spec, CharSequence appLabel, State state, boolean isSystem) {
Jason Monke24194f2016-02-05 14:55:05 -0500196 if (mSpecs.contains(spec)) {
197 return;
198 }
Jason Monk62b63a02016-02-02 15:15:31 -0500199 TileInfo info = new TileInfo();
200 info.state = state;
Jason Monk6f770562017-03-17 10:04:31 -0400201 info.state.dualTarget = false; // No dual targets in edit.
Jason Monke5b770e2017-03-03 21:49:29 -0500202 info.state.expandedAccessibilityClassName =
Jason Monk50a352b2016-06-13 12:43:47 -0400203 Button.class.getName();
Jason Monk62b63a02016-02-02 15:15:31 -0500204 info.spec = spec;
Amin Shaikhf7250472018-03-21 17:03:37 -0400205 info.state.secondaryLabel = (isSystem || TextUtils.equals(state.label, appLabel))
206 ? null : appLabel;
Jason Monk0b349ad2016-04-04 11:18:03 -0400207 info.isSystem = isSystem;
Jason Monke24194f2016-02-05 14:55:05 -0500208 mTiles.add(info);
209 mSpecs.add(spec);
Jason Monk62b63a02016-02-02 15:15:31 -0500210 }
211
Fabian Kozynski7753f3e2019-03-01 13:56:26 -0500212 private void createStateAndAddTile(
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400213 String spec, Drawable drawable, CharSequence label, CharSequence appLabel) {
Jason Monk62b63a02016-02-02 15:15:31 -0500214 QSTile.State state = new QSTile.State();
Fabian Kozynski7753f3e2019-03-01 13:56:26 -0500215 state.state = Tile.STATE_INACTIVE;
Jason Monk62b63a02016-02-02 15:15:31 -0500216 state.label = label;
217 state.contentDescription = label;
218 state.icon = new DrawableIcon(drawable);
Jason Monk0b349ad2016-04-04 11:18:03 -0400219 addTile(spec, appLabel, state, false);
Jason Monk62b63a02016-02-02 15:15:31 -0500220 }
221
222 public static class TileInfo {
223 public String spec;
224 public QSTile.State state;
Jason Monk0b349ad2016-04-04 11:18:03 -0400225 public boolean isSystem;
Jason Monk62b63a02016-02-02 15:15:31 -0500226 }
227
Jason Monk62b63a02016-02-02 15:15:31 -0500228 public interface TileStateListener {
229 void onTilesChanged(List<TileInfo> tiles);
230 }
231}