blob: 4dcacd4bffdcf6064eabc9e13306cb073e60a7e7 [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;
Jason Monk62b63a02016-02-02 15:15:31 -050030import android.service.quicksettings.TileService;
Amin Shaikhf7250472018-03-21 17:03:37 -040031import android.text.TextUtils;
Amin Shaikhc5b1bc82018-03-21 08:13:47 -040032import android.util.ArraySet;
Jason Monk50a352b2016-06-13 12:43:47 -040033import android.widget.Button;
34
Jason Monk9c7844c2017-01-18 15:21:53 -050035import com.android.systemui.Dependency;
Jason Monk62b63a02016-02-02 15:15:31 -050036import com.android.systemui.R;
Jason Monk702e2eb2017-03-03 16:53:44 -050037import com.android.systemui.plugins.qs.QSTile;
38import com.android.systemui.plugins.qs.QSTile.State;
Jason Monke5b770e2017-03-03 21:49:29 -050039import com.android.systemui.qs.QSTileHost;
Amin Shaikhc5b1bc82018-03-21 08:13:47 -040040import com.android.systemui.qs.external.CustomTile;
41import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
Dan Sandler4d90d1e2018-03-23 16:29:06 -040042import com.android.systemui.util.leak.GarbageMonitor;
Jason Monk62b63a02016-02-02 15:15:31 -050043
44import java.util.ArrayList;
Dan Sandler4d90d1e2018-03-23 16:29:06 -040045import java.util.Arrays;
Jason Monk62b63a02016-02-02 15:15:31 -050046import java.util.Collection;
47import java.util.List;
48
49public class TileQueryHelper {
Jason Monk62b63a02016-02-02 15:15:31 -050050 private static final String TAG = "TileQueryHelper";
51
52 private final ArrayList<TileInfo> mTiles = new ArrayList<>();
Amin Shaikhc5b1bc82018-03-21 08:13:47 -040053 private final ArraySet<String> mSpecs = new ArraySet<>();
54 private final Handler mBgHandler;
55 private final Handler mMainHandler;
Jason Monk62b63a02016-02-02 15:15:31 -050056 private final Context mContext;
Evan Laird6b284732017-02-28 17:27:04 -050057 private final TileStateListener mListener;
Jason Monk62b63a02016-02-02 15:15:31 -050058
Amin Shaikhc5b1bc82018-03-21 08:13:47 -040059 private boolean mFinished;
60
61 public TileQueryHelper(Context context, TileStateListener listener) {
Jason Monk62b63a02016-02-02 15:15:31 -050062 mContext = context;
Evan Laird6b284732017-02-28 17:27:04 -050063 mListener = listener;
Amin Shaikhc5b1bc82018-03-21 08:13:47 -040064 mBgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
65 mMainHandler = Dependency.get(Dependency.MAIN_HANDLER);
66 }
67
68 public void queryTiles(QSTileHost host) {
69 mTiles.clear();
70 mSpecs.clear();
71 mFinished = false;
72 // Enqueue jobs to fetch every system tile and then ever package tile.
Fabian Kozynskibb3ff872018-11-16 14:51:44 -050073 addCurrentAndStockTiles(host);
74
Amin Shaikhc5b1bc82018-03-21 08:13:47 -040075 addPackageTiles(host);
Jason Monk62b63a02016-02-02 15:15:31 -050076 }
77
Amin Shaikhc5b1bc82018-03-21 08:13:47 -040078 public boolean isFinished() {
79 return mFinished;
Evan Laird6b284732017-02-28 17:27:04 -050080 }
81
Fabian Kozynskibb3ff872018-11-16 14:51:44 -050082 private void addCurrentAndStockTiles(QSTileHost host) {
83 String stock = mContext.getString(R.string.quick_settings_tiles_stock);
84 String current = Settings.Secure.getString(mContext.getContentResolver(),
85 Settings.Secure.QS_TILES);
Dan Sandler4d90d1e2018-03-23 16:29:06 -040086 final ArrayList<String> possibleTiles = new ArrayList<>();
Fabian Kozynskibb3ff872018-11-16 14:51:44 -050087 if (current != null) {
88 // The setting QS_TILES is not populated immediately upon Factory Reset
89 possibleTiles.addAll(Arrays.asList(current.split(",")));
Fabian Kozynski565f2582018-12-19 17:39:53 -050090 } else {
91 current = "";
Fabian Kozynskibb3ff872018-11-16 14:51:44 -050092 }
93 String[] stockSplit = stock.split(",");
94 for (String spec : stockSplit) {
95 if (!current.contains(spec)) {
96 possibleTiles.add(spec);
97 }
98 }
99 if (Build.IS_DEBUGGABLE && !current.contains(GarbageMonitor.MemoryTile.TILE_SPEC)) {
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400100 possibleTiles.add(GarbageMonitor.MemoryTile.TILE_SPEC);
101 }
102
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400103 final ArrayList<QSTile> tilesToAdd = new ArrayList<>();
Dan Sandler4d90d1e2018-03-23 16:29:06 -0400104 for (String spec : possibleTiles) {
Fabian Kozynskibb3ff872018-11-16 14:51:44 -0500105 // Only add current and stock tiles that can be created from QSFactoryImpl
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400106 final QSTile tile = host.createTile(spec);
Yuta Yamada99fd2772016-10-06 17:40:19 +0900107 if (tile == null) {
108 continue;
109 } else if (!tile.isAvailable()) {
110 tile.destroy();
Jason Monk62b63a02016-02-02 15:15:31 -0500111 continue;
112 }
Jason Monke5107a32016-05-31 15:40:58 -0400113 tile.setListening(this, true);
Jason Monk62b63a02016-02-02 15:15:31 -0500114 tile.refreshState();
Jason Monke5107a32016-05-31 15:40:58 -0400115 tile.setListening(this, false);
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400116 tile.setTileSpec(spec);
117 tilesToAdd.add(tile);
Jason Monk62b63a02016-02-02 15:15:31 -0500118 }
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400119
120 mBgHandler.post(() -> {
121 for (QSTile tile : tilesToAdd) {
122 final QSTile.State state = tile.getState().copy();
123 // Ignore the current state and get the generic label instead.
124 state.label = tile.getTileLabel();
125 tile.destroy();
126 addTile(tile.getTileSpec(), null, state, true);
127 }
128 notifyTilesChanged(false);
129 });
Evan Laird6b284732017-02-28 17:27:04 -0500130 }
131
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400132 private void addPackageTiles(final QSTileHost host) {
133 mBgHandler.post(() -> {
134 Collection<QSTile> params = host.getTiles();
Evan Laird6b284732017-02-28 17:27:04 -0500135 PackageManager pm = mContext.getPackageManager();
136 List<ResolveInfo> services = pm.queryIntentServicesAsUser(
137 new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser());
138 String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock);
139
140 for (ResolveInfo info : services) {
141 String packageName = info.serviceInfo.packageName;
142 ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
143
144 // Don't include apps that are a part of the default tile set.
145 if (stockTiles.contains(componentName.flattenToString())) {
146 continue;
147 }
148
149 final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm);
150 String spec = CustomTile.toSpec(componentName);
151 State state = getState(params, spec);
152 if (state != null) {
153 addTile(spec, appLabel, state, false);
154 continue;
155 }
156 if (info.serviceInfo.icon == 0 && info.serviceInfo.applicationInfo.icon == 0) {
157 continue;
158 }
159 Drawable icon = info.serviceInfo.loadIcon(pm);
160 if (!permission.BIND_QUICK_SETTINGS_TILE.equals(info.serviceInfo.permission)) {
161 continue;
162 }
163 if (icon == null) {
164 continue;
165 }
166 icon.mutate();
167 icon.setTint(mContext.getColor(android.R.color.white));
168 CharSequence label = info.serviceInfo.loadLabel(pm);
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400169 addTile(spec, icon, label != null ? label.toString() : "null", appLabel);
Jason Monk62b63a02016-02-02 15:15:31 -0500170 }
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400171
172 notifyTilesChanged(true);
173 });
174 }
175
176 private void notifyTilesChanged(final boolean finished) {
177 final ArrayList<TileInfo> tilesToReturn = new ArrayList<>(mTiles);
178 mMainHandler.post(() -> {
179 mListener.onTilesChanged(tilesToReturn);
180 mFinished = finished;
Jason Monk62b63a02016-02-02 15:15:31 -0500181 });
182 }
183
Jason Monke5b770e2017-03-03 21:49:29 -0500184 private State getState(Collection<QSTile> tiles, String spec) {
185 for (QSTile tile : tiles) {
Evan Laird6b284732017-02-28 17:27:04 -0500186 if (spec.equals(tile.getTileSpec())) {
Jason Monk702e2eb2017-03-03 16:53:44 -0500187 return tile.getState().copy();
Evan Laird6b284732017-02-28 17:27:04 -0500188 }
189 }
190 return null;
Jason Monk62b63a02016-02-02 15:15:31 -0500191 }
192
Jason Monk0b349ad2016-04-04 11:18:03 -0400193 private void addTile(String spec, CharSequence appLabel, State state, boolean isSystem) {
Jason Monke24194f2016-02-05 14:55:05 -0500194 if (mSpecs.contains(spec)) {
195 return;
196 }
Jason Monk62b63a02016-02-02 15:15:31 -0500197 TileInfo info = new TileInfo();
198 info.state = state;
Jason Monk6f770562017-03-17 10:04:31 -0400199 info.state.dualTarget = false; // No dual targets in edit.
Jason Monke5b770e2017-03-03 21:49:29 -0500200 info.state.expandedAccessibilityClassName =
Jason Monk50a352b2016-06-13 12:43:47 -0400201 Button.class.getName();
Jason Monk62b63a02016-02-02 15:15:31 -0500202 info.spec = spec;
Amin Shaikhf7250472018-03-21 17:03:37 -0400203 info.state.secondaryLabel = (isSystem || TextUtils.equals(state.label, appLabel))
204 ? null : appLabel;
Jason Monk0b349ad2016-04-04 11:18:03 -0400205 info.isSystem = isSystem;
Jason Monke24194f2016-02-05 14:55:05 -0500206 mTiles.add(info);
207 mSpecs.add(spec);
Jason Monk62b63a02016-02-02 15:15:31 -0500208 }
209
Amin Shaikhc5b1bc82018-03-21 08:13:47 -0400210 private void addTile(
211 String spec, Drawable drawable, CharSequence label, CharSequence appLabel) {
Jason Monk62b63a02016-02-02 15:15:31 -0500212 QSTile.State state = new QSTile.State();
213 state.label = label;
214 state.contentDescription = label;
215 state.icon = new DrawableIcon(drawable);
Jason Monk0b349ad2016-04-04 11:18:03 -0400216 addTile(spec, appLabel, state, false);
Jason Monk62b63a02016-02-02 15:15:31 -0500217 }
218
219 public static class TileInfo {
220 public String spec;
221 public QSTile.State state;
Jason Monk0b349ad2016-04-04 11:18:03 -0400222 public boolean isSystem;
Jason Monk62b63a02016-02-02 15:15:31 -0500223 }
224
Jason Monk62b63a02016-02-02 15:15:31 -0500225 public interface TileStateListener {
226 void onTilesChanged(List<TileInfo> tiles);
227 }
228}