• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.systemui.qs.customize;
18 
19 import android.Manifest.permission;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.PackageManager;
24 import android.content.pm.ResolveInfo;
25 import android.graphics.drawable.Drawable;
26 import android.provider.Settings;
27 import android.service.quicksettings.Tile;
28 import android.service.quicksettings.TileService;
29 import android.text.TextUtils;
30 import android.util.ArraySet;
31 import android.widget.Button;
32 
33 import androidx.annotation.Nullable;
34 
35 import com.android.server.display.feature.flags.Flags;
36 import com.android.systemui.dagger.qualifiers.Background;
37 import com.android.systemui.dagger.qualifiers.Main;
38 import com.android.systemui.plugins.qs.QSTile;
39 import com.android.systemui.plugins.qs.QSTile.State;
40 import com.android.systemui.qs.QSHost;
41 import com.android.systemui.qs.dagger.QSScope;
42 import com.android.systemui.qs.external.CustomTile;
43 import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
44 import com.android.systemui.res.R;
45 import com.android.systemui.settings.UserTracker;
46 import com.android.systemui.shade.ShadeDisplayAware;
47 
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.Collection;
51 import java.util.List;
52 import java.util.concurrent.Executor;
53 
54 import javax.inject.Inject;
55 
56 /** */
57 @QSScope
58 public class TileQueryHelper {
59     private static final String TAG = "TileQueryHelper";
60 
61     private final ArrayList<TileInfo> mTiles = new ArrayList<>();
62     private final ArraySet<String> mSpecs = new ArraySet<>();
63     private final Executor mMainExecutor;
64     private final Executor mBgExecutor;
65     private final Context mContext;
66     private final UserTracker mUserTracker;
67     private TileStateListener mListener;
68 
69     private boolean mFinished;
70 
71     @Inject
TileQueryHelper( @hadeDisplayAware Context context, UserTracker userTracker, @Main Executor mainExecutor, @Background Executor bgExecutor )72     public TileQueryHelper(
73             @ShadeDisplayAware Context context,
74             UserTracker userTracker,
75             @Main Executor mainExecutor,
76             @Background Executor bgExecutor
77     ) {
78         mContext = context;
79         mMainExecutor = mainExecutor;
80         mBgExecutor = bgExecutor;
81         mUserTracker = userTracker;
82     }
83 
setListener(@ullable TileStateListener listener)84     public void setListener(@Nullable TileStateListener listener) {
85         mListener = listener;
86     }
87 
queryTiles(QSHost host)88     public void queryTiles(QSHost host) {
89         mTiles.clear();
90         mSpecs.clear();
91         mFinished = false;
92         // Enqueue jobs to fetch every system tile and then ever package tile.
93         addCurrentAndStockTiles(host);
94     }
95 
isFinished()96     public boolean isFinished() {
97         return mFinished;
98     }
99 
addCurrentAndStockTiles(QSHost host)100     private void addCurrentAndStockTiles(QSHost host) {
101         String stock = mContext.getString(R.string.quick_settings_tiles_stock);
102         String current = Settings.Secure.getString(mContext.getContentResolver(),
103                 Settings.Secure.QS_TILES);
104         final ArrayList<String> possibleTiles = new ArrayList<>();
105         if (current != null) {
106             // The setting QS_TILES is not populated immediately upon Factory Reset
107             possibleTiles.addAll(Arrays.asList(current.split(",")));
108         } else {
109             current = "";
110         }
111         String[] stockSplit =  stock.split(",");
112         for (String spec : stockSplit) {
113             if (!current.contains(spec)) {
114                 possibleTiles.add(spec);
115             }
116         }
117 
118         final ArrayList<QSTile> tilesToAdd = new ArrayList<>();
119         possibleTiles.remove("cell");
120         possibleTiles.remove("wifi");
121         if (Flags.evenDimmer() && mContext.getResources().getBoolean(
122                 com.android.internal.R.bool.config_evenDimmerEnabled)) {
123             possibleTiles.remove("reduce_brightness");
124         }
125 
126         for (String spec : possibleTiles) {
127             // Only add current and stock tiles that can be created from QSFactoryImpl.
128             // Do not include CustomTile. Those will be created by `addPackageTiles`.
129             if (spec.startsWith(CustomTile.PREFIX)) continue;
130             final QSTile tile = host.createTile(spec);
131             if (tile == null) {
132                 continue;
133             } else if (!tile.isAvailable()) {
134                 tile.destroy();
135                 continue;
136             }
137             tilesToAdd.add(tile);
138         }
139 
140         new TileCollector(tilesToAdd, host).startListening();
141     }
142 
143     private static class TilePair {
TilePair(QSTile tile)144         private TilePair(QSTile tile) {
145             mTile = tile;
146         }
147 
148         QSTile mTile;
149         boolean mReady = false;
150     }
151 
152     private class TileCollector implements QSTile.Callback {
153 
154         private final List<TilePair> mQSTileList = new ArrayList<>();
155         private final QSHost mQSHost;
156 
TileCollector(List<QSTile> tilesToAdd, QSHost host)157         TileCollector(List<QSTile> tilesToAdd, QSHost host) {
158             for (QSTile tile: tilesToAdd) {
159                 TilePair pair = new TilePair(tile);
160                 mQSTileList.add(pair);
161             }
162             mQSHost = host;
163             if (tilesToAdd.isEmpty()) {
164                 mBgExecutor.execute(this::finished);
165             }
166         }
167 
finished()168         private void finished() {
169             notifyTilesChanged(false);
170             addPackageTiles(mQSHost);
171         }
172 
startListening()173         private void startListening() {
174             for (TilePair pair: mQSTileList) {
175                 pair.mTile.addCallback(this);
176                 pair.mTile.setListening(this, true);
177                 // Make sure that at least one refresh state happens
178                 pair.mTile.refreshState();
179             }
180         }
181 
182         // This is called in the Bg thread
183         @Override
onStateChanged(State s)184         public void onStateChanged(State s) {
185             boolean allReady = true;
186             for (TilePair pair: mQSTileList) {
187                 if (!pair.mReady && pair.mTile.isTileReady()) {
188                     pair.mTile.removeCallback(this);
189                     pair.mTile.setListening(this, false);
190                     pair.mReady = true;
191                 } else if (!pair.mReady) {
192                     allReady = false;
193                 }
194             }
195             if (allReady) {
196                 for (TilePair pair : mQSTileList) {
197                     QSTile tile = pair.mTile;
198                     final QSTile.State state = tile.getState().copy();
199                     // Ignore the current state and get the generic label instead.
200                     state.label = tile.getTileLabel();
201                     tile.destroy();
202                     addTile(tile.getTileSpec(), null, state, true);
203                 }
204                 finished();
205             }
206         }
207     }
208 
addPackageTiles(final QSHost host)209     private void addPackageTiles(final QSHost host) {
210         mBgExecutor.execute(() -> {
211             Collection<QSTile> params = host.getTiles();
212             PackageManager pm = mContext.getPackageManager();
213             List<ResolveInfo> services = pm.queryIntentServicesAsUser(
214                     new Intent(TileService.ACTION_QS_TILE), 0, mUserTracker.getUserId());
215             String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock);
216 
217             for (ResolveInfo info : services) {
218                 String packageName = info.serviceInfo.packageName;
219                 ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
220 
221                 // Don't include apps that are a part of the default tile set.
222                 if (stockTiles.contains(componentName.flattenToString())) {
223                     continue;
224                 }
225 
226                 final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm);
227                 String spec = CustomTile.toSpec(componentName);
228                 State state = getState(params, spec);
229                 if (state != null) {
230                     addTile(spec, appLabel, state, false);
231                     continue;
232                 }
233                 if (info.serviceInfo.icon == 0 && info.serviceInfo.applicationInfo.icon == 0) {
234                     continue;
235                 }
236                 Drawable icon = info.serviceInfo.loadIcon(pm);
237                 if (!permission.BIND_QUICK_SETTINGS_TILE.equals(info.serviceInfo.permission)) {
238                     continue;
239                 }
240                 if (icon == null) {
241                     continue;
242                 }
243                 icon.mutate();
244                 icon.setTint(mContext.getColor(android.R.color.white));
245                 CharSequence label = info.serviceInfo.loadLabel(pm);
246                 createStateAndAddTile(spec, icon, label != null ? label.toString() : "null",
247                         appLabel);
248             }
249 
250             notifyTilesChanged(true);
251         });
252     }
253 
notifyTilesChanged(final boolean finished)254     private void notifyTilesChanged(final boolean finished) {
255         final ArrayList<TileInfo> tilesToReturn = new ArrayList<>(mTiles);
256         mMainExecutor.execute(() -> {
257             if (mListener != null) {
258                 mListener.onTilesChanged(tilesToReturn);
259             }
260             mFinished = finished;
261         });
262     }
263 
264     @Nullable
getState(Collection<QSTile> tiles, String spec)265     private State getState(Collection<QSTile> tiles, String spec) {
266         for (QSTile tile : tiles) {
267             if (spec.equals(tile.getTileSpec())) {
268                 if (tile.isTileReady()) {
269                     return tile.getState().copy();
270                 } else {
271                     return null;
272                 }
273             }
274         }
275         return null;
276     }
277 
addTile( String spec, @Nullable CharSequence appLabel, State state, boolean isSystem)278     private void addTile(
279             String spec, @Nullable CharSequence appLabel, State state, boolean isSystem) {
280         if (mSpecs.contains(spec)) {
281             return;
282         }
283         state.dualTarget = false; // No dual targets in edit.
284         state.expandedAccessibilityClassName = Button.class.getName();
285         state.secondaryLabel = (isSystem || TextUtils.equals(state.label, appLabel))
286                 ? null : appLabel;
287         TileInfo info = new TileInfo(spec, state, isSystem);
288         mTiles.add(info);
289         mSpecs.add(spec);
290     }
291 
createStateAndAddTile( String spec, Drawable drawable, CharSequence label, CharSequence appLabel)292     private void createStateAndAddTile(
293             String spec, Drawable drawable, CharSequence label, CharSequence appLabel) {
294         QSTile.State state = new QSTile.State();
295         state.state = Tile.STATE_INACTIVE;
296         state.label = label;
297         state.contentDescription = label;
298         state.icon = new DrawableIcon(drawable);
299         addTile(spec, appLabel, state, false);
300     }
301 
302     public static class TileInfo {
TileInfo(String spec, QSTile.State state, boolean isSystem)303         public TileInfo(String spec, QSTile.State state, boolean isSystem) {
304             this.spec = spec;
305             this.state = state;
306             this.isSystem = isSystem;
307         }
308 
309         public String spec;
310         public QSTile.State state;
311         public boolean isSystem;
312     }
313 
314     public interface TileStateListener {
onTilesChanged(List<TileInfo> tiles)315         void onTilesChanged(List<TileInfo> tiles);
316     }
317 }
318